Skip to content

feat(use-cases): add GPU music production agent sample - #1982

Open
rmncardoso wants to merge 3 commits into
awslabs:mainfrom
rmncardoso:feat/gpu-music-production-agent
Open

feat(use-cases): add GPU music production agent sample#1982
rmncardoso wants to merge 3 commits into
awslabs:mainfrom
rmncardoso:feat/gpu-music-production-agent

Conversation

@rmncardoso

Copy link
Copy Markdown
Contributor

Concise description of the PR

Adds a use-case sample under 02-use-cases/02-workflow-automation-agents/ in which
three collaborating agents produce and vet a music track inside one AgentCore
Runtime Instances session, because the repo has no sample that runs a model on a
GPU or shows several runtimes sharing one instance and one volume.

Three agents, one runtimeSessionId, one g6.xlarge:

Agent Artifact Compute Role
composition_agent.py container (ECR) GPU — NVIDIA L4 A Bedrock model writes a structured brief; ACE-Step v1 3.5B (Apache-2.0) then renders real audio locally on the instance GPU
mastering_agent.py container (ECR) CPU Measures the mix, hands the measurements to the model, which returns an EQ/compressor/limiter chain; the DSP applies it and the output is measured again
compliance_agent.py zip on S3 CPU Re-measures independently, screens against a generated back-catalogue using chroma features and subsequence DTW across all 12 transpositions, and computes the verdict rather than asking a model for it

What it demonstrates that no existing sample does:

  • A GPU instance type on a capacity provider. The three existing capacity-provider samples (03-mcp-, 08-http-, 09-a2a-ec2-capacity-provider) all default to m6g.large. This is the first GPU sample in the repo.
  • Local model inference on a persistent volume. A CUDA torch stack is 3.13 GB of wheels against a 2 GB image cap, so the 14.6 GB stack is built onto a capacity provider EBS volume by a mode=prepare step and invoked as a subprocess. That volume survives stop/resume.
  • Collocation as the coordination primitive. All three runtimes are invoked with the same runtimeSessionId, so they land on one instance and pass real audio files through /mnt/tracks — no queue, no database, no orchestrator.
  • Mixed artifacts on one capacity provider. Two container images plus one zip-on-S3 code artifact, which also surfaces that the two artifact types run as different Linux identities on the shared mount.
  • An agent invoking another agent's runtime. When the screen objects, the compliance agent calls the composition runtime with its own session id, so remediation happens on the instance it is already running on.
  • A computed verdict. passed is arithmetic on the measurements; the model only writes the explanation and is not shown the distances or thresholds at all.

User experience

Before. Nothing in the repo runs a model on a GPU, and nothing shows more than one runtime sharing an instance. A builder asking "can I host my own model on AgentCore, and how do several agents hand large artifacts to each other?" has no worked example — the capacity-provider samples are single-agent and CPU-only.

After. Four commands produce audio you can play:

export AWS_REGION=us-east-2
python scripts/deploy.py                    # creates everything, launches nothing
python scripts/invoke.py --with-catalogue   # ~7 min; the GPU starts billing here
python scripts/cleanup.py                   # deletes the SESSION - that is what stops it

The run prints the evidence rather than asserting it:

  3. compose (renders audio on the GPU)
    rendered : NVIDIA L4 in 8.98s (peak VRAM 7.63 GiB)
    audio     20.062s  48000Hz  2ch  -7.5 LUFS  peak 0.42 dBTP

  4. master (real DSP, verified by measurement)
    read     : composition.wav  <- written by another agent
    before    -7.5 LUFS  peak 0.42 dBTP
    after     -14.0 LUFS  peak -3.2 dBTP
    targets  : loudness met, true peak held

  5. compliance screen
    verdict  : REVIEW REQUIRED
    screen   : 2 reference(s), closest catalogue_00.wav distance 0.0665 (review)

  -- collocation --
    All 5 steps were served by one instance, as intended.

You end with composition.wav, master.wav and three markdown reports in
runs/<track>/. The mastering report records what the model decided and what it
deliberately left alone
— on the verified run it placed a highpass at 30 Hz to
remove a DC offset it had measured at 0.001171, and bypassed the compressor
entirely because the loudness range was only 0.8 LU.

Cost is explicit. deploy.py creates no instances; billing starts at the first
invoke and stops when the session is deleted, not when a runtime is stopped.
cleanup.py deletes the session first for exactly that reason.

Testing

Deployed and run end to end on a live g6.xlarge in us-east-2, then torn down and
verified clean (0 instances, 0 orphaned volumes, no roles or buckets left). Confirmed
by measurement, not by the agents' own reports: the audio was re-measured locally and
matched (-7.5 → -14.0 LUFS, +0.42 → -3.2 dBTP), and the raw render's 9 clipped
runs at full scale became 0 in the master. The compliance zip came in at 82.0 MB
compressed / 235.0 MB uncompressed against the 250 MB / 750 MB limits.

Three collaborating agents produce and vet a music track inside one
AgentCore Runtime Instances session on a single GPU instance.

- composition (container, GPU): a Bedrock model writes the brief, then
  ACE-Step v1 3.5B renders real audio on the instance's NVIDIA L4
- mastering (container, CPU): measures the mix, a model picks an
  EQ/compressor/limiter chain from those numbers, DSP applies it, the
  output is measured again
- compliance (zip on S3, CPU): re-measures independently, screens against
  a generated back-catalogue with chroma features and subsequence DTW over
  all 12 transpositions, and computes the verdict rather than asking a
  model for it

Demonstrates capacity providers with a GPU instance type, local model
inference on a persistent EBS volume, collocation of three runtimes via a
shared runtimeSessionId, mixed container and zip artifacts on one capacity
provider, and an agent invoking another agent's runtime for remediation.

Verified end to end on a live g6.xlarge in us-east-2.
@github-actions github-actions Bot added the 02-use-cases 02-use-cases label Aug 21, 2026
@github-actions

github-actions Bot commented Aug 22, 2026

Copy link
Copy Markdown

Latest scan for commit: 65595cb | Updated: 2026-08-22 22:42:07 UTC

Security Scan Results

Scan Metadata

  • Project: ASH
  • Scan executed: 2026-08-22T22:41:51+00:00
  • ASH version: 3.0.0

Summary

Scanner Results

The table below shows findings by scanner, with status based on severity thresholds and dependencies:

Column Explanations:

Severity Levels (S/C/H/M/L/I):

  • Suppressed (S): Security findings that have been explicitly suppressed/ignored and don't affect the scanner's pass/fail status
  • Critical (C): The most severe security vulnerabilities requiring immediate remediation (e.g., SQL injection, remote code execution)
  • High (H): Serious security vulnerabilities that should be addressed promptly (e.g., authentication bypasses, privilege escalation)
  • Medium (M): Moderate security risks that should be addressed in normal development cycles (e.g., weak encryption, input validation issues)
  • Low (L): Minor security concerns with limited impact (e.g., information disclosure, weak recommendations)
  • Info (I): Informational findings for awareness with minimal security risk (e.g., code quality suggestions, best practice recommendations)

Other Columns:

  • Time: Duration taken by each scanner to complete its analysis
  • Action: Total number of actionable findings at or above the configured severity threshold that require attention

Scanner Results:

  • PASSED: Scanner found no security issues at or above the configured severity threshold - code is clean for this scanner
  • FAILED: Scanner found security vulnerabilities at or above the threshold that require attention and remediation
  • MISSING: Scanner could not run because required dependencies/tools are not installed or available
  • SKIPPED: Scanner was intentionally disabled or excluded from this scan
  • ERROR: Scanner encountered an execution error and could not complete successfully

Severity Thresholds (Thresh Column):

  • CRITICAL: Only Critical severity findings cause scanner to fail
  • HIGH: High and Critical severity findings cause scanner to fail
  • MEDIUM (MED): Medium, High, and Critical severity findings cause scanner to fail
  • LOW: Low, Medium, High, and Critical severity findings cause scanner to fail
  • ALL: Any finding of any severity level causes scanner to fail

Threshold Source: Values in parentheses indicate where the threshold is configured:

  • (g) = global: Set in the global_settings section of ASH configuration
  • (c) = config: Set in the individual scanner configuration section
  • (s) = scanner: Default threshold built into the scanner itself

Statistics calculation:

  • All statistics are calculated from the final aggregated SARIF report
  • Suppressed findings are counted separately and do not contribute to actionable findings
  • Scanner status is determined by comparing actionable findings to the threshold
Scanner S C H M L I Time Action Result Thresh
bandit 0 6 0 0 19 0 1.6s 6 FAILED MED (g)
cdk-nag 0 0 0 0 0 0 6.9s 0 PASSED MED (g)
cfn-nag 0 0 0 0 0 0 8ms 0 PASSED MED (g)
checkov 0 2 0 0 0 0 6.4s 2 FAILED MED (g)
detect-secrets 0 0 0 0 0 0 1.2s 0 PASSED MED (g)
grype 0 0 0 0 0 0 1m 0s 0 PASSED MED (g)
npm-audit 0 0 0 0 0 0 269ms 0 PASSED MED (g)
opengrep 0 0 0 0 0 0 <1ms 0 SKIPPED MED (g)
semgrep 0 0 0 0 0 0 <1ms 0 MISSING MED (g)
syft 0 0 0 0 0 0 2.4s 0 PASSED MED (g)

Detailed Findings

Show 8 actionable findings

Finding 1: B104

  • Severity: HIGH
  • Scanner: bandit
  • Rule ID: B104
  • Location: 02-use-cases/02-workflow-automation-agents/gpu-music-production-agent/compliance_agent.py:812-813

Description:
Possible binding to all interfaces.

Code Snippet:

# Explicit bind and explicit port: see the note in composition_agent.py.
    app.run(host="0.0.0.0", port=int(os.environ.get("PORT", "8080")))

Finding 2: B104

  • Severity: HIGH
  • Scanner: bandit
  • Rule ID: B104
  • Location: 02-use-cases/02-workflow-automation-agents/gpu-music-production-agent/composition_agent.py:645-646

Description:
Possible binding to all interfaces.

Code Snippet:

# reach port 8080. AgentCore sets PORT, which run() does not consult.
    app.run(host="0.0.0.0", port=int(os.environ.get("PORT", "8080")))

Finding 3: B104

  • Severity: HIGH
  • Scanner: bandit
  • Rule ID: B104
  • Location: 02-use-cases/02-workflow-automation-agents/gpu-music-production-agent/mastering_agent.py:495-496

Description:
Possible binding to all interfaces.

Code Snippet:

# reach it. AgentCore sets PORT, which run() does not consult.
    app.run(host="0.0.0.0", port=int(os.environ.get("PORT", "8080")))

Finding 4: B310

  • Severity: HIGH
  • Scanner: bandit
  • Rule ID: B310
  • Location: 02-use-cases/02-workflow-automation-agents/gpu-music-production-agent/model_stack/prepare.py:87-89

Description:
Audit url open for permitted schemes. Allowing use of file:/ or custom schemes is often unexpected.

Code Snippet:

t0 = time.time()
    with urllib.request.urlopen(ACESTEP_TARBALL, timeout=180) as r, open(archive, "wb") as fh:
        shutil.copyfileobj(r, fh)

Finding 5: B202

  • Severity: HIGH
  • Scanner: bandit
  • Rule ID: B202
  • Location: 02-use-cases/02-workflow-automation-agents/gpu-music-production-agent/model_stack/prepare.py:97-99

Description:
tarfile.extractall used without any validation. Please check and discard dangerous members.

Code Snippet:

raise RuntimeError(f"unsafe tar member {member.name!r}")
        tf.extractall(src_root)
    archive.unlink(missing_ok=True)

Finding 6: B310

  • Severity: HIGH
  • Scanner: bandit
  • Rule ID: B310
  • Location: 02-use-cases/02-workflow-automation-agents/gpu-music-production-agent/scripts/invoke.py:151-153

Description:
Audit url open for permitted schemes. Allowing use of file:/ or custom schemes is often unexpected.

Code Snippet:

try:
            with urllib.request.urlopen(url, timeout=300) as r, open(dest, "wb") as fh:
                fh.write(r.read())

Finding 7: CKV_DOCKER_3

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV_DOCKER_3
  • Location: 02-use-cases/02-workflow-automation-agents/gpu-music-production-agent/Dockerfile.mastering:1-24

Description:
Ensure that a user for the container has been created

Code Snippet:

# Mastering agent image. No RUN steps, for the same reason as
# Dockerfile.composition: dependencies are vendored for linux/amd64 on the build
# host so this builds without QEMU emulation.
#
# This agent does no GPU work. It runs on the same GPU instance as the composition
# agent because collocation is what gives it access to the rendered audio, but
# mastering is filters and gain, so keeping it on the CPU leaves the GPU free for
# generation and avoids two processes competing for VRAM.
FROM --platform=linux/amd64 public.ecr.aws/docker/library/python:3.12-slim

WORKDIR /app

COPY build/agentdeps /app/deps
ENV PYTHONPATH=/app/deps
ENV PYTHONUNBUFFERED=1

COPY audio_dsp.py mastering_agent.py /app/

EXPOSE 8080

HEALTHCHECK --interval=30s --timeout=5s --start-period=20s \
    CMD python -c "import urllib.request;urllib.request.urlopen('http://127.0.0.1:8080/ping',timeout=3)"

CMD ["python", "mastering_agent.py"]

Finding 8: CKV_DOCKER_3

  • Severity: HIGH
  • Scanner: checkov
  • Rule ID: CKV_DOCKER_3
  • Location: 02-use-cases/02-workflow-automation-agents/gpu-music-production-agent/Dockerfile.composition:1-53

Description:
Ensure that a user for the container has been created

Code Snippet:

# Composition agent image.
#
# Deliberately has NO RUN steps. Every dependency is vendored on the build host
# with `uv --python-platform x86_64-manylinux2014` and copied in, so this builds
# for linux/amd64 from an arm64 Mac in seconds. A single `pip install` here would
# execute under QEMU emulation instead.
#
# GPU families on AgentCore capacity providers are all x86_64, so the platform is
# pinned rather than inherited.
#
# Measured: 180 MB, against the 2 GB hard cap on an AgentCore Runtime image. The
# generative stack (torch + CUDA + ACE-Step, about 6.3 GB installed) does not fit
# in any image and lives on the capacity provider's `models` volume instead;
# model_stack/prepare.py builds it there on first use.
FROM --platform=linux/amd64 public.ecr.aws/docker/library/python:3.12-slim

WORKDIR /app

# Vendored wheels, unpacked by deploy.py into build/agentdeps.
COPY build/agentdeps /app/deps
ENV PYTHONPATH=/app/deps
ENV PYTHONUNBUFFERED=1

# AgentCore injects the NVIDIA driver into /usr/lib64 at run time. This base image
# is Debian, whose dynamic linker searches /usr/lib/x86_64-linux-gnu and /usr/lib
# but NOT /usr/lib64 -- there is no entry for it in /etc/ld.so.conf.d, and
# ldconfig cannot help because the driver appears after the image is built.
#
# Without this, libcuda.so.1 is present on disk and still unloadable, so torch
# reports cuda_available=False and silently runs on the CPU. Measured on a live
# g6.xlarge: `ctypes.CDLL("libcuda.so.1")` raises "cannot open shared object
# file", and the same call with LD_LIBRARY_PATH=/usr/lib64 succeeds and torch then
# reports one NVIDIA L4.
#
# An Amazon Linux based image would not need this, because /usr/lib64 is a
# standard library path there.
ENV LD_LIBRARY_PATH=/usr/lib64

COPY audio_dsp.py composition_agent.py /app/
COPY model_stack /app/model_stack

# Runs as root so the agent can write to the capacity provider volume. The mount
# is 2775 root:agentcore-runtime-user and the agent process holds that
# supplementary group; a command shell in the same runtime does not, which is why
# only the agent can populate the volume. Drop privileges only if you move the
# workspace to an EFS or S3 Files access point where you set the POSIX UID/GID.

EXPOSE 8080

HEALTHCHECK --interval=30s --timeout=5s --start-period=20s \
    CMD python -c "import urllib.request;urllib.request.urlopen('http://127.0.0.1:8080/ping',timeout=3)"

CMD ["python", "composition_agent.py"]

Report generated by Automated Security Helper (ASH) at 2026-08-22T22:41:46+00:00

CI runs `ruff check` and `ruff format --check` on changed files. Fixes:

- PLW1510: pass check=False explicitly to the five subprocess.run calls
  that inspect returncode themselves or deliberately tolerate failure
- RUF046: drop redundant int() around round(); round() returns a Python
  int even for numpy floats, verified
- EXE001: mark the four scripts/ entry points executable, they have shebangs
- RUF100, I001, ISC004, F541: unused noqa, import order, implicit string
  concatenation in collections, f-string without placeholders
- ruff format at the repo's line-length of 120

DSP output is byte-identical before and after: the same two rendered WAVs
re-measure to the same loudness, true peak, LRA and chroma-DTW distance.
The README said deleting the session was "the only thing that stops EC2 and
EBS billing". The AgentCore documentation says otherwise: deleting a capacity
provider also "stops and deletes all of its associated sessions and their
persistent storage".

Session deletion is still the right first step, but because it is the fastest
path -- the capacity provider route requires disassociating every runtime and
runtime version first, and that detachment is asynchronous -- not because it is
the only one. Reworded to "the fastest way".
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

02-use-cases 02-use-cases

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant