Skip to content

feat(oam): migrate agentic OAM components from appmod-blueprints#18

Open
shapirov103 wants to merge 84 commits into
mainfrom
feature/oam-for-agents
Open

feat(oam): migrate agentic OAM components from appmod-blueprints#18
shapirov103 wants to merge 84 commits into
mainfrom
feature/oam-for-agents

Conversation

@shapirov103

Copy link
Copy Markdown
Contributor

Summary

Moves the OAM artifacts that are specific to the agentic platform out of appmod-blueprints and into this repo, leaving generic platform components (appmod-service, dp-service-account, dynamodb-, rds-, s3-*, ingress trait, platform-env config) where they belong. KubeVela itself continues to be provisioned by the platform — this PR ships only the agentic ComponentDefinitions and supporting artifacts.

What changed

Path Files Purpose
platform/oam/ 17 CUE sources for agent, mcp-server, agentcore-memory ComponentDefinitions, plus generate.sh, README, DESIGN, and 11 examples
gitops/addons/charts/oam-agent-components/ 4 Thin Helm chart packaging the three ComponentDefinition manifests for cluster registration
applications/strands-agent-base/ 29 Reference Strands agent app (Dockerfile, build scripts, unit + integration tests, A2A and gateway fixtures)

Total: 50 files, +5920 lines, no deletions.

Migration scope rationale

This PR was deliberately scoped to agentic-only artifacts. Things explicitly left in appmod-blueprints:

  • Generic platform CUE: appmod-service, dp-service-account, dynamodb-ack, dynamodb-table, rds-cluster, rds-cluster-mysql, s3-bucket, s3-bucket-ack, external-database-secret, platform-env (config)
  • Generic platform traits: ingress, path-based-ingress, component-policy
  • KubeVela helm chart itself (gitops/addons/charts/kubevela/) — platform installs vela-core
  • TODO.md (about platform-env config — generic concern)

Things deliberately not migrated even though they exist on the source branch:

  • mcp-gateway-operator chart — out of scope per current iteration
  • agentgateway wrapper chart — duplicates this repo's existing split (agentgateway/ controller + agent-gateway/ resources + litellm/ chart). Bedrock-backend and litellm pieces from the wrapper are redundant here.
  • gitops/agentic/ parallel hierarchy + agentic-addons.yaml ApplicationSet — this repo uses a single unified addon hierarchy
  • Source's bootstrap addons.yaml diff — mostly version regressions (argocd 9.5.12→9.4.9, cert-manager v1.20.2→v1.19.4, crossplane 2.2.1→2.2.0, mlflow 1.8.1→1.7.2, etc.); this repo is ahead on all shared addon versions

Open items (intentional follow-ups, not blockers)

  1. Addon registration: oam-agent-components chart is not yet wired into gitops/addons/bootstrap/default/addons.yaml. Waiting on the platform-side KubeVela provisioning work in feature/agent-platform to land first. Suggested entry once ready:

    oam-agent-components:
      enabled: false
      namespace: vela-system
      defaultVersion: '0.1.0'
      path: 'gitops/addons/charts/oam-agent-components'
      annotationsAppSet:
        argocd.argoproj.io/sync-wave: '5'   # after kubevela
      selector:
        matchExpressions:
          - key: enable_oam_agent_components
            operator: In
            values: ['true']
  2. Doc references not yet updated: platform/oam/README.md still references applications/strands/ (path renamed to strands-agent-base/ in this PR) and DESIGN.md mentions platform components like appmod-service.cue that aren't in this repo. These will be cleaned in a follow-up commit on this branch before merging.

  3. Source-branch cleanup: corresponding deletions in appmod-blueprints (removing the migrated agentic files) will land in a separate PR there.

Tested

  • git archive | tar faithful copy verified — line counts match source for all 3 generated ComponentDefinition YAMLs (agent.yaml 296L, mcp-server.yaml 265L, agentcore-memory.yaml 85L)
  • No .venv / __pycache__ / build artifacts leaked into the strands-agent-base copy
  • Nothing staged outside the three target directories
  • helm template gitops/addons/charts/oam-agent-components/ smoke test
  • End-to-end: helm install against a cluster with KubeVela already running → ComponentDefinitions registered → example Application deploys

Source provenance

Branch: feature/oam-for-agents in aws-samples/appmod-blueprints
Source HEAD at migration: 222fa7648c24cdf40f97fbf12fdef14d7e83e1f8

Move the OAM artifacts that are specific to the agentic platform out of
appmod-blueprints, leaving generic platform components (appmod-service,
dp-service-account, dynamodb-*, rds-*, s3-*, ingress trait, platform-env)
behind. KubeVela itself continues to be provisioned by the platform.

- platform/oam/: CUE sources for agent, mcp-server, agentcore-memory
  ComponentDefinitions, plus generate.sh, README, DESIGN, and 11 examples
  covering minimal/simple/MCP/AgentCore-memory/Milvus/session-affinity flows.

- gitops/addons/charts/oam-agent-components/: thin Helm chart packaging the
  three ComponentDefinition manifests so they can be applied as a standard
  addon once KubeVela is running. Chart only \u2014 no addon registration entry
  yet (waiting on platform-side KubeVela provisioning to land).

- applications/strands-agent-base/: reference Strands agent app (Dockerfile
  with python:3.13-slim-bookworm + apt CVE patching, build/podman scripts,
  unit + integration tests, A2A and gateway integration fixtures).
@shapirov103 shapirov103 requested a review from ashoksrirama June 16, 2026 22:05
The fleet-secret chart on each cluster reads enable_<addon> flags from
gitops/overlays/environments/<env>/enabled-addons.yaml and writes them as
labels on the ArgoCD cluster secret. ApplicationSets then match those
labels to deploy addons.

Previously:
  hub:    crossplane=true, kubevela=false  → no KubeVela anywhere
  dev:    no crossplane, no crossplane_aws, no kubevela entries
  prod:   same as dev

Result was that crossplane-agentcore-spoke-{dev,prod} apps had been
retrying ~3400 times against spokes that had no Crossplane CRDs.

This change:
  hub:    flip kubevela to true
  dev:    add crossplane: true, crossplane_aws: true, kubevela: true
  prod:   same as dev

After fleet-secret reconciles, the cluster secret labels propagate and
Crossplane + AWS providers + KubeVela install via existing
ApplicationSets in the platform repo. The agentcore Compositions and
Provider that the spoke apps are trying to apply will then have CRDs to
land on.
Two related changes for the agentic platform:

1. Remove kagent operator (no longer needed)
   - Drop kagent-crds, kagent, kagent-setup, kagent-monitoring from
     bootstrap/default/addons.yaml
   - Drop the same enable flags from environments/control-plane/addons.yaml
   - Delete gitops/addons/charts/kagent-setup/, kagent-monitoring/, and
     gitops/addons/default/addons/kagent/ values dir
   - Rename namespaces previously colocated with kagent:
       litellm:                  kagent → litellm
       prometheus-operator-crds: kagent → monitoring
   - Rename selector enable_kagent_monitoring → enable_monitoring on
     prometheus-operator-crds
   - Update README, Taskfile, scripts/bootstrap.sh to drop kagent
     references
   - Rewrite gitops/DEPLOYMENT.md to reflect the kagent-free deployment
   - Clean up platform/oam/README.md (drop kagent-rollout-component
     references, generic namespace examples, fix file list to match the
     migrated content)

   ArgoCD will prune the 12 kagent Applications (kagent-crds, kagent,
   kagent-setup, kagent-monitoring × 3 clusters) on next sync.

2. Register oam-agent-components addon (KubeVela ComponentDefinitions
   for agent, mcp-server, agentcore-memory)
   - Add addon definition in bootstrap/default/addons.yaml at sync-wave 5
     (after kubevela, which is at sync-wave 3 on the platform side)
   - Selector enable_oam_components (no-op when global selector
     enable_agent_platform is in effect)
   - Enable in environments/control-plane/addons.yaml

   Once KubeVela is installed (via the kubevela addon enabled in the
   prior commit fba4afb), this addon registers the three agentic
   ComponentDefinitions cluster-wide so OAM Applications can use them.
- Add deps: [agentic:ensure-kubeconfig] to bootstrap, status, destroy
- Pass --context {{.HUB_CLUSTER_NAME}} explicitly to every kubectl call
  so the active kubeconfig context cannot leak through (e.g., from a
  rebooted kind cluster on 127.0.0.1)
- Use --alias {{.HUB_CLUSTER_NAME}} on update-kubeconfig so the context
  has a clean short name instead of the EKS ARN
- Add cluster-info validation to ensure-kubeconfig with a clear success
  message; fails early with a clear error if creds/network are bad
The agent and mcp-server ComponentDefinitions in oam-agent-components
emit argoproj.io/v1alpha1 Rollout resources. KubeVela's mutating
admission webhook does pre-render validation and rejects them with:

  no matches for kind "Rollout" in version "argoproj.io/v1alpha1"

Enabling argo_rollouts on all 3 overlays so the platform-side
ApplicationSet (registered in appmod-blueprints) deploys the Argo
Rollouts controller + CRDs to all clusters.

Validated by:
  kubectl get applications.argoproj.io oam-agent-components-* -A
  -> message: 'admission webhook denied request: no matches for kind
     "Rollout"'
  Only agentcore-memory ComponentDefinition registered; agent and
  mcp-server were dropped due to admission failure.
The chart is a wave-5 declarative consumer in the addon-wave taxonomy
documented in appmod-blueprints/gitops/addons/registry/README.md
(see appmod-blueprints PR #751). Its ComponentDefinitions reference
resource kinds delivered by lower-wave addons:
  wave 3: argo-rollouts (Rollout CRD), crossplane (Composition)
  wave 4: kubevela (ComponentDefinition CRD and webhook)

Adding new ComponentDefinitions here does not change the chart's
wave — only changes to the set of consumed addons do.

The dependency on these addons is enforced by ArgoCD sync-wave
ordering, not by Helm chart `dependencies:` which would bundle
copies of the addons (each is its own ArgoCD Application).

- Chart.yaml: rewritten description with explicit prerequisites
- README.md: new file with role explanation, prerequisite table,
  lifecycle notes, and a pointer to the upstream wave taxonomy
…th, repoint Strands

- Add local umbrella chart gitops/addons/charts/bifrost wrapping upstream
  maximhq/bifrost 2.1.16 and bundling, in one Application:
  - Crossplane Role + PodIdentityAssociation granting bedrock:InvokeModel* to
    the bifrost ServiceAccount (EKS Pod Identity, not IRSA)
  - ExternalSecret for the encryption key + platform virtual key
  - vendored subchart (Chart.lock + charts/*.tgz) for clean-state ArgoCD render
- Bedrock provider via IAM (no static keys); expose Claude Sonnet 4.5 as the
  alias 'claude-sonnet' -> us.anthropic.claude-sonnet-4-5-20250929-v1:0
- Enable Bifrost governance (is_vk_mandatory) with a budget, rate-limit, and
  platform virtual key; agents authenticate via the x-bf-vk header
- Switch bifrost addon to local path with per-cluster templated valuesObject
- Repoint Strands agent from LiteLLM to Strands OpenAIModel against Bifrost /v1
  (OAM agent ComponentDefinition default + app config/deployment)
- Remove ExternalSecret template and externalSecret values block; customers
  do not need Secrets Manager
- bootstrap.sh step 5 now creates the bifrost namespace + generates
  bifrost-secrets (BIFROST_ENCRYPTION_KEY + BIFROST_PLATFORM_VK) via
  openssl rand; prints the VK at the end so the operator can pass it to
  agents via LLM_GATEWAY_API_KEY / modelConfig.llmGatewayApiKey
- Idempotent: skips if bifrost-secrets already exists
- Remove secretManagerKey from addon valuesObject
…and spokes

- Replace per-cluster bootstrap.sh Secret with ExternalSecret reading from
  account-level SM path 'open-agentic-platform/bifrost' (works on hub and
  all spoke clusters without per-cluster action)
- Add Taskfile task agentic:bifrost-secrets: idempotent, auto-generates
  BIFROST_ENCRYPTION_KEY + BIFROST_PLATFORM_VK, creates the SM secret,
  prints the VK for operator use; runs automatically as dep of agentic:bootstrap
- Remove bifrost step from bootstrap.sh (wrong layer — doesn't reach spokes)
- No customer action required: task agentic:install handles everything
…test

- Add bifrost: true to control-plane, dev, prod enabled-addons overlays
  so the addon actually deploys (was missing — Bifrost would never start)
- Update OAM example files to Bifrost /v1 URL
- Rewrite integration test for OpenAIModel + x-bf-vk (drop LiteLLMModel)
…ng design doc

- Drop ExternalSecret template, externalSecret values blocks, envFrom,
  encryptionKeySecret — Bifrost runs without secrets until per-workload
  VK minting is implemented
- Remove agentic:bifrost-secrets Taskfile task (no longer needed)
- Add gitops/addons/charts/bifrost/DESIGN.md capturing per-workload VK
  minting architecture (KubeVela workflow step → management API → k8s Secret)
- is_vk_mandatory remains false; TODO comment points to DESIGN.md
…ment not StatefulSet

Config changes now roll out without delete-and-recreate. Postgres StatefulSet
spec is stable. TODO: migrate to RDS with passwordCommand + Pod Identity.
Langfuse ExternalSecret was reading <aws_cluster_name>/keycloak-clients
which resolves to spoke-dev/keycloak-clients — a path that doesn't exist.
The keycloak config Job runs on the hub and writes all client secrets to
<hub_cluster_name>/keycloak-clients (e.g. peeks-hub/keycloak-clients).

Fix: use hub_cluster_name annotation (already set on every spoke cluster
secret by the fleet-secret chart) so all langfuse instances — hub and
spokes — read from the same Secrets Manager path.
…stead

The agentcore chart created a PodIdentityAssociation (eks.aws.upbound.io)
on the spoke. Spokes have no EKS-API Crossplane provider (they never
provision clusters), so the association could never reconcile -> agentcore
provider had no credentials -> all AgentCore resources stuck OutOfSync.

Gate the chart's Role + PodIdentityAssociation behind podIdentity.createViaChart
(default false). The agentcore provider's Pod Identity is now provisioned by
the hub's Crossplane via the pod-identities overlay, which targets the spoke
cluster from the hub (where the working EKS provider lives).
The client-provisioner ApplicationSet reads fleet-member values from the
fleet repo (this install uses OAP as fleetRepo) and templates the chart
path as platform-charts/<client_provisioner>-client-provisioner. Without
the label the path rendered platform-charts/<no value>-client-provisioner
and the generated Applications were stuck in Unknown/app-path-does-not-exist.

Adds client_provisioner: keycloak so the path resolves to
keycloak-client-provisioner (chart lives in the addons repo).
… gate

Reverts the hub-overlay workaround. The agentcore provider's IAM Role +
PodIdentityAssociation are created by the chart and reconciled locally by
the spoke's credentialed iam/eks providers — consistent with bifrost and
every other workload. No central per-workload identity registration.
hmuthusamy and others added 30 commits June 30, 2026 09:37
…itecture

- probabilistic_sampler at 100% (never drop agent spans)
- memory_limiter with percentage-based limits (85%/10% spike)
- batch processor (1024/500ms/2048) for efficient export
- otlphttp/langfuse with 30s timeout + retry_on_failure (5s→30s, 5m max)
- prometheus receiver scraping Bifrost at :8080/metrics
- Two pipelines: traces (otlp→langfuse+xray), metrics (prometheus→xray)
- Langfuse exporter conditional on endpoint being set (prevents crash)
- Matches the target architecture diagram
…oesn't support metrics)

awsxray exporter only supports traces. The metrics pipeline (Bifrost
prometheus scrape) uses debug exporter until AMP remote write is configured.
…b cluster

The hub cluster secret doesn't have hub_cluster_name annotation (set
by Crossplane for spokes only). Use default 'hub' to prevent
ApplicationSet template rendering failure.
…nnotation

Go template .metadata.annotations.hub_cluster_name errors on missing
key before default can evaluate. Use index function which returns
empty string for missing keys, then pipe to default.
…ays on hub)

The hub cluster secret lacks hub_cluster_name annotation and Go template
index/default functions don't work with ArgoCD ApplicationSet's template
engine for missing map keys. Since Langfuse always runs on hub, the SM
path is always hub/langfuse-otel regardless of which cluster consumes it.
The OTel collector on spoke clusters reads Langfuse auth from SM
via ExternalSecret. The hub Langfuse chart creates the source K8s
Secret and a PushSecret that syncs it to hub/langfuse-otel in SM.

Flow: Langfuse chart (hub) → K8s Secret → PushSecret → SM → ExternalSecret (spokes) → OTel collector env var
PushSecret stores values as SecretBinary. ExternalSecret needs
decodingStrategy:Auto to handle both binary and string formats.
PushSecret stores as SecretBinary which corrupts the key when
ExternalSecret reads it back. Use aws-cli Job to write as SecretString
(proper JSON) so ExternalSecret can extract properties correctly.
The push-otel-keys Job needs AWS credentials to write to SM.
Creates an IAM Role with secretsmanager:Create/Put/Describe/Get
scoped to hub/langfuse-* secrets, and a PodIdentityAssociation
binding langfuse-seed SA to that role.
…auth change

The collector pod starts with optional LANGFUSE_OTEL_AUTH env var.
If the secret didn't exist at startup, the env is empty and never
reloads. Adding a checksum annotation ensures the Deployment spec
changes when the secret config changes, triggering a rollout.
…INT is set

The agent should emit traces via the collector (not just direct-to-Langfuse).
When OTEL_EXPORTER_OTLP_ENDPOINT points to the local collector, initialize
StrandsTelemetry without auth headers (collector handles auth to Langfuse).
…ackend

Architecture: Agent → Collector → Langfuse (traces) + debug (metrics)
X-Ray is not part of the target architecture.
1. Cost tracking: Seed CronJob registers claude-sonnet model pricing
   in Langfuse via API (idempotent). Bifrost OTEL spans will have
   canonical model names matching Langfuse built-in pricing.

2. AMP remote write: Collector supports prometheusremotewrite/amp
   exporter when amp.enabled=true and endpoint is set. Falls back
   to debug exporter when AMP is not configured.

3. Bifrost W3C traceparent: Enable Bifrost OTEL plugin pointing to
   the local OTel collector (http :4318). Bifrost emits child spans
   with gen_ai.* attributes (tokens, cost, model) that merge with
   agent root spans in the collector.

Also: Fix Bifrost values.yaml hardcoded region (was us-west-2,
now empty — set dynamically by ApplicationSet).
- Add sigv4auth extension for AWS authentication
- Add prometheusremotewrite/amp exporter (conditional on amp.enabled)
- Add Pod Identity (IAM Role + PodIdentityAssociation) for aps:RemoteWrite
- Pass AMP endpoint from addons.yaml to all spoke collectors
- Metrics pipeline: Bifrost prometheus scrape → batch → AMP remote write
…fig change

The OTel collector reads config only at startup. Without a checksum
annotation tracking config-affecting values, ArgoCD syncs update the
ConfigMap but the pod continues using stale config (e.g. debug exporter
instead of prometheusremotewrite/amp). This annotation forces a rolling
restart whenever the Langfuse endpoint, Bifrost scrape target, or AMP
settings change.
…ter name

1. AMP remote write endpoint: replaced hardcoded workspace ID
   (ws-1d91195d-cb39-470d-827a-9625b3574df4) with a templated annotation
   reference (amp_remote_write_endpoint). Each deployment must set this
   annotation on the cluster secret. When absent, AMP is gracefully
   disabled (falls back to debug exporter).

2. Langfuse pod-identity: replaced hardcoded clusterName 'hub' with
   a templated value from the ApplicationSet (aws_cluster_name annotation).
   This fixes the ResourceNotFoundException when EKS can't find a cluster
   named 'hub' in environments where the cluster has a different name.

Both issues were reported by users running fresh installs where the
cluster names and AMP workspace IDs differ from the original dev env.
The agent image uses opentelemetry-exporter-otlp-proto-http which only
supports HTTP protocol. The CUE ComponentDefinition was injecting port
4317 (gRPC), causing the agent to hang during telemetry init and fail
liveness probes.

Both the agent SDK and Bifrost OTEL plugin use HTTP (:4318). Updated
the CUE default and OBSERVABILITY.md to match.
…-east-1

The langfuse chart's pod-identity uses global.awsRegion for the
PodIdentityAssociation region. It was defaulting to us-east-1 because
the ApplicationSet valuesObject was not passing global.awsRegion.

Clusters in us-west-2 (or any other region) would fail with:
  ResourceNotFoundException: No cluster found for name: hub

Fix:
- Add global.awsRegion to langfuse valuesObject (from aws_region annotation)
- Change values.yaml default from 'us-east-1' to empty string (forces
  injection from ApplicationSet, never silently falls back to wrong region)
…ctorMatchLabels

The appset-chart uses useSelectors: false with globalSelectors
(enable_agent_platform), meaning the per-addon selector.matchExpressions
is NOT used as a generator filter. All addons generate apps for every
cluster with enable_agent_platform=true.

This caused langfuse-spoke-dev and langfuse-spoke-prod ApplicationSet
apps to be created, which fail because the pod-identity references a
cluster name that doesn't match (langfuse is hub-only infrastructure).

Fix: Add selectorMatchLabels.enable_langfuse=true to the langfuse addon.
The appset-chart template renders selectorMatchLabels into the generator's
matchLabels alongside globalSelectors. Since only the hub cluster secret
has enable_langfuse=true, the ApplicationSet will only generate the
langfuse-hub app.
The appset-chart only generates an ApplicationSet when enabled: true.
With enabled: false, no ApplicationSet is created regardless of cluster
labels. The per-addon selectorMatchLabels controls which clusters get
the app (e.g. enable_langfuse for hub-only), but the ApplicationSet
must exist first.

Enabled: langfuse, otel-collector, bifrost, agent-gateway, gateway-api-crds,
agentgateway, agentgateway-crds, oam-agent-components, crossplane-agentcore,
prometheus-operator-crds.

Kept disabled: litellm (replaced by Bifrost), jaeger (replaced by Langfuse).
litellm is replaced by Bifrost. jaeger is replaced by Langfuse.
The litellm ApplicationSet was blocking the agent-platform-addons sync
because EKS ArgoCD waits for ApplicationSet health checks which stall.
Allows per-addon matchLabels (e.g. enable_langfuse) to be merged into
the cluster generator selector alongside globalSelectors. This restricts
langfuse to hub-only without needing useSelectors: true.
- Changed all hardcoded 'us-east-1' defaults to 'us-west-2' in langfuse
  pod-identity, push-otel-secret, and otel-collector values.
- Added 'langfuse: true' to control-plane enabled-addons.yaml so the
  fleet-secret chart sets enable_langfuse label on the hub cluster secret.
  Combined with selectorMatchLabels in the langfuse addon definition,
  this ensures the langfuse ApplicationSet only generates apps for
  clusters with the label (hub only).
The appmod-blueprints task install writes amp_endpoint_url annotation
to the cluster secret (via Secrets Manager). Aligned the otel-collector
addon to read from this annotation and append api/v1/remote_write.

When the annotation is absent (not yet provisioned), the template
renders empty and AMP is gracefully disabled (falls back to debug).
The collector template already appends api/v1/remote_write to the
remoteWriteEndpoint value. The addons.yaml was also appending it,
resulting in a double path that returns 404.
The CUE source was updated but the generated YAML in
oam-agent-components/templates/agent.yaml was not regenerated.
The PlatformCluster claims are GitOps-managed: the fleet `clusters`
ApplicationSet generates `clusters-<env>` Application(s) that own the
claims with automated prune. Deleting a claim directly makes ArgoCD
immediately re-sync and recreate it, spinning up a fresh spoke EKS + VPC
while the old one is still deleting.

Pause the ApplicationSet and disable self-heal on the generated
Application(s) before deleting the claims so they stay deleted and
Crossplane can drain the spokes cleanly.
fix(spokes:destroy): pause GitOps generator before deleting claims
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.

3 participants