feat(oabctl,studio-cp): enable ACP by default on Studio-deployed agents - #127
Merged
Conversation
openab-gateway's own default for OPENAB_ACP_ENABLED is off when unset
(confirmed: `.unwrap_or(false)` at every call site in openab's lib.rs/
main.rs) — Brett wants Studio-deployed agents to default to ACP on.
- manifest.rs: new `Spec.acp_enabled: Option<bool>`. No serde default and
no struct-level "on" default — omitted (None) preserves today's
behavior everywhere else (existing manifests, `oabctl create`'s CLI
wizard, fleet expand()). Only Studio's own first-deploy path sets it.
- apply.rs (ECS) / k8s_driver.rs (k8s): push OPENAB_ACP_ENABLED=true into
the container env when acp_enabled == Some(true). Never push "false" —
absence already means off.
- studio-cp: build_default_manifest / build_default_k8s_manifest (the
"this agent has never been provisioned" path — redeploys of an
already-stored manifest are untouched) now set acp_enabled: Some(true)
and provision a matching OPENAB_ACP_AUTH_KEY:
- ECS: generated + stored in Secrets Manager under the same
oab/{namespace}/{name} convention `oabctl create`'s CLI wizard uses
for the Discord bot token (oabctl::create::store_secret, now pub).
- k8s: generated + server-side-applied into a new k8s Secret
({name}-acp, same namespace the Deployment lands in), referenced via
the existing k8s-secret://<name>#<key> scheme k8s_driver.rs already
resolves for spec.secrets.
The auth key is mandatory, not optional: openab-gateway's default bind is
0.0.0.0:8080 (GATEWAY_LISTEN), not loopback, and the fail-open (keyless)
exception in acp_auth_ok_for_bind only applies to a loopback bind — a
keyless non-loopback bind just silently fails to mount /acp.
Ref #119.
brettchien
added a commit
that referenced
this pull request
Aug 29, 2026
…rver-side
Brett's catch: this form's output is ultimately a config.toml for the
created agent, and that file can also be produced by an admin agent
calling the same tool directly (not through Studio's UI) — for those to
stay in sync, the actual TOML-rendering logic can't live in the console
(TypeScript), it has to be the single server-side source of truth both
callers go through.
deploy_provision_agent's config_toml:string param is replaced with
structured fields (api_key, chat_platform, chat_bot_token,
chat_channel_secret) — studio-cp's new generate_agent_config() renders the
actual text (the structured-input counterpart of oabctl create's
generate_config, generalized from Discord-only to discord/telegram/line).
Any caller sending the same fields — the wizard or a future admin agent —
gets byte-identical config.toml by construction, not by convention.
provision_agent_secrets() stores the secret-bearing fields in the same
oab/{namespace}/{name} Secrets Manager convention oabctl create already
uses for the Discord token — a separate secret from #127's ACP auth key,
since these feed config.toml's [secrets.refs]/${secrets.x} (openab's own
resolution, aws-sm:// only) while the ACP key is a container-level env var
injected via spec.secrets, a different delivery path entirely.
k8s deploys refuse a non-empty chat_platform rather than silently
deploying something broken: config.toml's secret resolution only
understands aws-sm://, and k8s_driver.rs's build_deployment injects no AWS
credentials into the pod at all (confirmed by reading it) — so a k8s pod
has no way to actually resolve that URI at runtime. ACP-only k8s deploys
are unaffected (already-working, different mechanism).
api_key is captured/stored but not yet wired into config.toml — which env
var a given vendor's CLI expects it under needs vendor-specific research
this round didn't do. Flagged in the tool schema and struct doc comment
rather than fabricating a config key nothing reads.
Ref #128.
brettchien
added a commit
that referenced
this pull request
Aug 29, 2026
…#128) (#129) * feat(studio-cp,oab-mcp,src-tauri): add deploy_provision_agent (studio#128) New Fleet wizard direction (Brett, this thread): drop the compose-library Template/Overlay model, replace with a vendor + chat-platform + ACP flow that composes config.toml directly. Confirmed earlier (#128 investigation) that provision_manifest/provision_k8s don't require a Bundle produced by compose_named — a hand-built Bundle{image_tag, files} works identically. This PR adds the backend capability only (all 3 layers: MCP tool, Tauri bridge, studio-cp core) — purely additive, doesn't touch the existing deploy_provision/compose-library path at all. Console wiring (replacing the Template/Overlay UI with the new wizard) is a separate follow-up PR. - studio-cp: provision_agent / provision_agent_k8s — near-duplicates of provision_from_library[_k8s] from "resolve the bucket" onward (same create-vs-redeploy branch, same pre_seed hook injection, same bundle upload), except the Bundle's config.toml comes from the caller directly instead of studio_compose::compose_named(library, template, overlay). Deliberate duplication over a shared refactor, matching the tradeoff provision_from_library_k8s's own doc comment already made for the same reason (avoid risking the already-landed functions' shape). - oab-mcp: new deploy_provision_agent tool (config_toml + image + name, same provider/context/expected_principal/fleet/cluster args as deploy_provision minus library/template/overlay), dispatches to the new studio-cp functions. Tool-count test updated (17 -> 18). - src-tauri: deploy_provision_agent bridge command, mirroring deploy_provision's shape, registered in generate_handler!. Ref #128. * refactor(studio-cp,oab-mcp,src-tauri): move config.toml generation server-side Brett's catch: this form's output is ultimately a config.toml for the created agent, and that file can also be produced by an admin agent calling the same tool directly (not through Studio's UI) — for those to stay in sync, the actual TOML-rendering logic can't live in the console (TypeScript), it has to be the single server-side source of truth both callers go through. deploy_provision_agent's config_toml:string param is replaced with structured fields (api_key, chat_platform, chat_bot_token, chat_channel_secret) — studio-cp's new generate_agent_config() renders the actual text (the structured-input counterpart of oabctl create's generate_config, generalized from Discord-only to discord/telegram/line). Any caller sending the same fields — the wizard or a future admin agent — gets byte-identical config.toml by construction, not by convention. provision_agent_secrets() stores the secret-bearing fields in the same oab/{namespace}/{name} Secrets Manager convention oabctl create already uses for the Discord token — a separate secret from #127's ACP auth key, since these feed config.toml's [secrets.refs]/${secrets.x} (openab's own resolution, aws-sm:// only) while the ACP key is a container-level env var injected via spec.secrets, a different delivery path entirely. k8s deploys refuse a non-empty chat_platform rather than silently deploying something broken: config.toml's secret resolution only understands aws-sm://, and k8s_driver.rs's build_deployment injects no AWS credentials into the pod at all (confirmed by reading it) — so a k8s pod has no way to actually resolve that URI at runtime. ACP-only k8s deploys are unaffected (already-working, different mechanism). api_key is captured/stored but not yet wired into config.toml — which env var a given vendor's CLI expects it under needs vendor-specific research this round didn't do. Flagged in the tool schema and struct doc comment rather than fabricating a config key nothing reads. Ref #128.
brettchien
added a commit
that referenced
this pull request
Aug 29, 2026
…write (studio#128) (#132) Replaces the Template/Overlay compose-library step (empty on every fresh install by design, blocking the whole deploy flow — the original #119 screenshot that started this) with the vendor + chat platform + ACP flow confirmed with Brett across #128's design thread. ## Console (items 1-5 of the runbook, shipped together — all fields live on the same new Step 2 screen) - Vendor <select>: Claude/codex/agy/cursor/kiro. "agy" is only the display label — the option value is "antigravity" (the GHCR image variant / Dockerfile.package build-target name resolve_vendor_image_tags expects). - Image tag: pre-filled from resolve_vendor_image_tags (#131) — Stable if GHCR confirms it exists, else Beta (pre-beta-<vendor>), else left for manual entry. Always editable — no lookup failure blocks the field. - API key (optional): captured, stored as a secret server-side; not yet wired into config.toml (documented gap from #129, unchanged here). - Chat platform (optional, default none — "use ACP directly"): Discord/Telegram/LINE, reveals the right token field(s) per platform. k8s deploys refuse a non-empty selection both client-side (clean validation message) and server-side (#129's existing refusal) — config.toml secret resolution needs AWS credentials a k8s pod doesn't have. - Enable ACP checkbox: default checked, forced off + disabled for vendor=agy (its bridge bypasses openab-gateway's /acp route entirely). - Agent name: pre-filled with a random Greek god name, shuffle button to re-roll, still freely editable. - No separate "Preview bundle" step anymore — fill in the fields, Deploy. ## Backend: acp_enabled made caller-controlled (was hardcoded true in #127) #127 hardcoded `acp_enabled: Some(true)` inside build_default_manifest / build_default_k8s_manifest with no way for a caller to turn it off — fine when nothing exposed a toggle, not fine once the wizard has an actual checkbox (needed for the agy case above). Both functions, and AgentWizardInput, now take `acp_enabled: bool` explicitly: - studio-cp: build_default_manifest/build_default_k8s_manifest gained an `acp_enabled: bool` param — only generates the ACP auth secret when true. provision_from_library[_k8s] (the older compose-library path, unrelated to this wizard) pass `true` at their call sites, preserving #127's original unconditional-on behavior there unchanged. - oab-mcp: deploy_provision_agent's schema gained `acp_enabled: boolean`, defaulting to true when the caller omits it (same default #127 had, now explicit and overridable instead of baked in). - src-tauri: bridge command threads the new param through. Ref #128.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Follow-up to #119. Confirmed with Brett:
OPENAB_ACP_ENABLEDdefaults to off in openab-gateway when unset (.unwrap_or(false)at every call site —openab-gateway/src/lib.rs:620,src/main.rs:192-194,src/main.rs:1156-1159). He wants Studio-deployed agents to default to it being on.Change
manifest.rs: newSpec.acp_enabled: Option<bool>. Deliberately no serde default and no "on by default" at the schema level —Nonepreserves today's behavior for everything that doesn't explicitly set it (existing manifests,oabctl create's CLI wizard, fleetexpand()). Only Studio's own first-deploy path opts in.apply.rs(ECS) /k8s_driver.rs(k8s): pushOPENAB_ACP_ENABLED=trueinto the container env whenacp_enabled == Some(true). Never push"false"— absence already means off, matches the existingBOOTSTRAP_FROM-style conditional-push pattern right above it in both files.studio-cp:build_default_manifest/build_default_k8s_manifest— the "this agent has never been provisioned before" path (redeploys of an already-stored manifest are untouched, same as always) — now setacp_enabled: Some(true)and provision a matchingOPENAB_ACP_AUTH_KEY:uuidv4) and stored in Secrets Manager under the sameoab/{namespace}/{name}conventionoabctl create's CLI wizard already uses for the Discord bot token — reuses itsstore_secrethelper (bumped topub).Patch::Apply, idempotent) into a new k8sSecret({name}-acp, same namespace theDeploymentlands in perk8s_driver.rs's ownApi::namespaced(client, &m.metadata.namespace)), referenced via the existingk8s-secret://<name>#<key>schemek8s_driver.rsalready resolves forspec.secrets— no new resolution mechanism needed.Why the auth key isn't optional:
openab-gateway's default bind is0.0.0.0:8080(GATEWAY_LISTEN), not loopback.acp_auth_ok_for_bind's fail-open (keyless) exception only applies to a genuinely loopback bind — a keyless non-loopback bind just silently fails to mount/acp(logged, not fatal, but the tunnel wouldn't come up). So enabling ACP without also provisioning the key would be a no-op that looks like it worked.Verification
aws-sdk-ec2OOM limitation as every other Rust PR in this batch (fix(oab-mcp): install rustls CryptoProvider before first TLS handshake (studio#119) #120/feat(studio-cp): mint GKE tokens via gcp_auth instead of exec plugin (studio#119) #123).Secretobjects):kube::Api::create/patchsignatures,PatchParams::apply,Patch::Apply, andk8s_openapi::api::core::v1::Secret's exact fields (metadata: ObjectMeta,string_data: Option<BTreeMap<String, String>>) all confirmed via docs.rs for kube 0.99.0 / k8s-openapi 0.24.0 before writing this.Spec { ... }struct-literal construction site in the workspace was found and updated for the new field (rgconfirms 4 sites:manifest.rs's fleetexpand(),k8s_driver.rs's test fixture, and the twostudio-cpfunctions this PR's logic lives in) — a missed one would be a compile error, not a silent bug, so this is at least mechanically checkable even without a local build.build-testjob is the real compile gate./acpendpoint actually comes up — this is genuinely new, previously-unexercised code (secret creation on both platforms), can't be verified any other way in this environment.Ref #119.
🤖 Generated with Claude Code