Skip to content

feat(oabctl,studio-cp): enable ACP by default on Studio-deployed agents - #127

Merged
brettchien merged 1 commit into
mainfrom
feat/acp-enabled-by-default-119
Aug 29, 2026
Merged

feat(oabctl,studio-cp): enable ACP by default on Studio-deployed agents#127
brettchien merged 1 commit into
mainfrom
feat/acp-enabled-by-default-119

Conversation

@brettchien

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #119. Confirmed with Brett: OPENAB_ACP_ENABLED defaults 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: new Spec.acp_enabled: Option<bool>. Deliberately no serde default and no "on by default" at the schema level — None preserves today's behavior for everything that doesn't explicitly set it (existing manifests, oabctl create's CLI wizard, fleet expand()). Only Studio's own first-deploy path opts in.
  • 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, matches the existing BOOTSTRAP_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 set acp_enabled: Some(true) and provision a matching OPENAB_ACP_AUTH_KEY:
    • ECS: generated (uuid v4) and stored in Secrets Manager under the same oab/{namespace}/{name} convention oabctl create's CLI wizard already uses for the Discord bot token — reuses its store_secret helper (bumped to pub).
    • k8s: generated and server-side-applied (Patch::Apply, idempotent) into a new k8s Secret ({name}-acp, same namespace the Deployment lands in per k8s_driver.rs's own Api::namespaced(client, &m.metadata.namespace)), referenced via the existing k8s-secret://<name>#<key> scheme k8s_driver.rs already resolves for spec.secrets — no new resolution mechanism needed.

Why the auth key isn't optional: openab-gateway's default bind is 0.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

  • Not locally compiled — same pre-existing aws-sdk-ec2 OOM 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).
  • Every new API surface was checked against docs.rs before use, since this is new ground (no existing code in this repo creates k8s Secret objects): kube::Api::create/patch signatures, PatchParams::apply, Patch::Apply, and k8s_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.
  • Every Spec { ... } struct-literal construction site in the workspace was found and updated for the new field (rg confirms 4 sites: manifest.rs's fleet expand(), k8s_driver.rs's test fixture, and the two studio-cp functions 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.
  • CI's build-test job is the real compile gate.
  • Recommend Brett re-test a fresh "+ New fleet" deploy (both ECS and k8s) after this merges and check the agent's /acp endpoint 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

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
brettchien merged commit 0486ce8 into main Aug 29, 2026
2 checks passed
@brettchien
brettchien deleted the feat/acp-enabled-by-default-119 branch August 29, 2026 03:01
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant