@aws/agentcore 1.0.0-preview.30 → 1.0.0-preview.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -6
- package/dist/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap +1103 -1
- package/dist/assets/cdk/package.json +1 -1
- package/dist/assets/python/http/bma/base/Dockerfile +41 -0
- package/dist/assets/python/http/bma/base/README.md +122 -0
- package/dist/assets/python/http/bma/base/bma-acr-policy.json +11 -0
- package/dist/assets/python/http/bma/base/client.py +132 -0
- package/dist/assets/python/http/bma/base/lifecycle/server.py +641 -0
- package/dist/assets/python/http/bma/base/otel/collector.yaml +69 -0
- package/dist/assets/python/http/bma/base/plugins/acr-report/.codex-plugin/plugin.json +6 -0
- package/dist/assets/python/http/bma/base/plugins/acr-report/skills/acr-report/SKILL.md +16 -0
- package/dist/assets/python/http/bma/base/pyproject.toml +19 -0
- package/dist/cli/index.mjs +570 -635
- package/dist/schema/constants.d.ts +4 -1
- package/dist/schema/constants.d.ts.map +1 -1
- package/dist/schema/constants.js +22 -3
- package/dist/schema/constants.js.map +1 -1
- package/npm-shrinkwrap.json +3 -30
- package/package.json +2 -2
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
FROM public.ecr.aws/lambda/microvms:al2023-minimal
|
|
2
|
+
|
|
3
|
+
# OS setup
|
|
4
|
+
RUN dnf install -y tar gzip findutils shadow-utils ca-certificates \
|
|
5
|
+
&& dnf clean all \
|
|
6
|
+
&& useradd -m -u 1000 app
|
|
7
|
+
|
|
8
|
+
# Codex
|
|
9
|
+
RUN curl -fsSL https://chatgpt.com/codex/install.sh -o /tmp/install-codex.sh \
|
|
10
|
+
&& CODEX_NON_INTERACTIVE=1 CODEX_INSTALL_DIR=/opt/bma/bin CODEX_HOME=/opt/bma/codex \
|
|
11
|
+
sh /tmp/install-codex.sh \
|
|
12
|
+
&& chmod -R a+rX /opt/bma \
|
|
13
|
+
&& rm /tmp/install-codex.sh
|
|
14
|
+
|
|
15
|
+
# CloudWatch agent
|
|
16
|
+
RUN arch=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/') \
|
|
17
|
+
&& curl -fsSL -o /tmp/cwagent.rpm \
|
|
18
|
+
https://amazoncloudwatch-agent.s3.amazonaws.com/amazon_linux/${arch}/latest/amazon-cloudwatch-agent.rpm \
|
|
19
|
+
&& rpm -i /tmp/cwagent.rpm \
|
|
20
|
+
&& rm /tmp/cwagent.rpm
|
|
21
|
+
|
|
22
|
+
# uv and Python
|
|
23
|
+
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/
|
|
24
|
+
ARG UV_DEFAULT_INDEX
|
|
25
|
+
ARG UV_INDEX
|
|
26
|
+
ENV UV_PYTHON_INSTALL_DIR=/opt/python UV_PYTHON_BIN_DIR=/usr/local/bin
|
|
27
|
+
RUN uv python install --default
|
|
28
|
+
|
|
29
|
+
# Application. The layout of /opt/bma is the same as the layout of this directory.
|
|
30
|
+
WORKDIR /opt/bma
|
|
31
|
+
ENV UV_COMPILE_BYTECODE=1 UV_NO_PROGRESS=1 \
|
|
32
|
+
UV_DEFAULT_INDEX=${UV_DEFAULT_INDEX} UV_INDEX=${UV_INDEX}
|
|
33
|
+
COPY pyproject.toml uv.lock* ./
|
|
34
|
+
RUN uv sync --no-dev
|
|
35
|
+
COPY lifecycle/ lifecycle/
|
|
36
|
+
COPY otel/ otel/
|
|
37
|
+
COPY plugins/ plugins/
|
|
38
|
+
USER app
|
|
39
|
+
|
|
40
|
+
EXPOSE 8080
|
|
41
|
+
CMD ["uv", "run", "--no-sync", "opentelemetry-instrument", "python", "-u", "lifecycle/server.py"]
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
This is a Bedrock Managed Agents (BMA) environment generated by the AgentCore CLI.
|
|
2
|
+
|
|
3
|
+
# Layout
|
|
4
|
+
|
|
5
|
+
BMA runs the agent loop (Codex) in the Bedrock Managed Agents service. This AgentCore Runtime (ACR) is the customer
|
|
6
|
+
environment where BMA runs commands. The ACR has no model code.
|
|
7
|
+
|
|
8
|
+
| Path | Description |
|
|
9
|
+
| --- | --- |
|
|
10
|
+
| `Dockerfile` | The ACR image. It installs the Codex CLI with OpenAI's installer, the CloudWatch agent, Python with uv, and the dependencies in `pyproject.toml`. It copies `lifecycle/`, `otel/`, and `plugins/` to the same paths under `/opt/bma`, the working directory of the image. |
|
|
11
|
+
| `lifecycle/server.py` | The environment lifecycle server, `bma-acr-lifecycle`. It handles the lifecycle calls from BMA and starts `codex exec-server`. |
|
|
12
|
+
| `otel/collector.yaml` | The configuration of the CloudWatch agent. The agent gets the spans and logs of `codex exec-server`, puts the session ID on them, and sends them to X-Ray and CloudWatch Logs with the ACR role. To turn off observability, add `DISABLE_ADOT_OBSERVABILITY` with the value `true` to `envVars`. |
|
|
13
|
+
| `plugins/acr-report` | A Codex plugin with the `acr-report` skill. The skill saves the Python version, the user ID, and the working directory in `acr-report.txt`. |
|
|
14
|
+
| `bma-acr-policy.json` | Lets the ACR role call `bedrock-mantle:RegisterEnvironment` and `bedrock-mantle:ConnectEnvironment` on every Mantle project. To limit the role to your projects, change `Resource` to `arn:aws:bedrock-mantle:<region>:<account-id>:project/<project-id>`. |
|
|
15
|
+
| `pyproject.toml` | The Python dependencies. `aws-opentelemetry-distro` sends a span for each call from BMA and a child span for each step of the call, for example the state load or the exec-server start. `bedrock-agentcore` is the AgentCore SDK. The `dev` group has the dependencies of `client.py`, and the image does not install it. Each image build installs the latest Python and the latest releases. To pin them, run `uv lock` and keep `uv.lock` next to this file. |
|
|
16
|
+
| `client.py` | A sample OpenAI SDK client. It creates a session in BMA, and BMA sends the session's commands to this ACR. |
|
|
17
|
+
|
|
18
|
+
Do not change `lifecycle/server.py`. It must match the lifecycle calls that BMA makes.
|
|
19
|
+
|
|
20
|
+
The server always sends HTTP 200, because the Runtime changes any other status to 424 and drops the body. If a call
|
|
21
|
+
fails, the body has the HTTP status code of the failure in `status_code` and the reason in `error`, for example
|
|
22
|
+
`{"error": "the exec-server did not start", "status_code": 503}`.
|
|
23
|
+
|
|
24
|
+
# Codex version
|
|
25
|
+
|
|
26
|
+
The image build installs the latest Codex release. The server needs Codex 0.154.0 or newer because it runs
|
|
27
|
+
`codex exec-server`. To pin a release, set `CODEX_RELEASE` next to `CODEX_NON_INTERACTIVE` in the `Dockerfile`. The
|
|
28
|
+
image build downloads Codex and Python from the internet, so a build in VPC mode needs a route to the internet.
|
|
29
|
+
|
|
30
|
+
# ACR settings
|
|
31
|
+
|
|
32
|
+
`agentcore create` writes these settings to `agentcore/agentcore.json`:
|
|
33
|
+
|
|
34
|
+
- An idle timeout of 1800 seconds (30 minutes) and a maximum lifetime of 28800
|
|
35
|
+
seconds (8 hours).
|
|
36
|
+
- No session storage. Session storage is only for a microVM Runtime, so without it the same settings work on a
|
|
37
|
+
capacity provider.
|
|
38
|
+
|
|
39
|
+
A value that you give to `agentcore create` replaces the value above.
|
|
40
|
+
|
|
41
|
+
The server keeps the connection state in `state.json` in `BMA_STATE_DIR`. The default is `/home/app/.bma`. The client
|
|
42
|
+
sets the workspace in `workspace_directory` when it creates the session. `client.py` uses `/home/app/workspace`. The
|
|
43
|
+
server creates that directory and runs the agent commands in it. If the activate call has no workspace directory, the
|
|
44
|
+
server returns status 400. If the server cannot create the directory, it returns status 503, logs
|
|
45
|
+
`workspace_unavailable`, and does not start the exec-server.
|
|
46
|
+
|
|
47
|
+
Files in the home directory do not stay after an idle stop. On a microVM Runtime, to keep the files and the connection
|
|
48
|
+
state after an idle stop, put the home directory on session storage:
|
|
49
|
+
|
|
50
|
+
1. Add session storage with `--session-storage-mount-path /mnt/home`.
|
|
51
|
+
2. Set `BMA_HOME_DIR` to `/mnt/home` in `envVars`. Then `state.json` is in `/mnt/home/.bma`, and `CODEX_HOME` is
|
|
52
|
+
`/mnt/home/.codex`.
|
|
53
|
+
3. Set `WORKSPACE_DIRECTORY` in `client.py` to `/mnt/home/workspace`.
|
|
54
|
+
|
|
55
|
+
If you set `WORKSPACE_DIRECTORY` to a path on session storage, the Runtime must have session storage. If not, the
|
|
56
|
+
server cannot create the workspace, and the session fails. If `BMA_STATE_DIR` is not on a mounted path, or the server
|
|
57
|
+
cannot create it, the server keeps `state.json` in `.bma` in `BMA_HOME_DIR`.
|
|
58
|
+
|
|
59
|
+
# Environment variables
|
|
60
|
+
|
|
61
|
+
To change a setting of the server, add the variable to `envVars` of the agent in `agentcore/agentcore.json`. Then run
|
|
62
|
+
`agentcore deploy`. For example:
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
"envVars": [
|
|
66
|
+
{ "name": "BMA_MAX_TURN_LEASE", "value": "600" },
|
|
67
|
+
{ "name": "BMA_CODEX_BINARY", "value": "/opt/bma/bin/codex" }
|
|
68
|
+
]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
| Variable | Default | Description |
|
|
72
|
+
| --- | --- | --- |
|
|
73
|
+
| `DISABLE_ADOT_OBSERVABILITY` | Not set | Set to `true` to turn off the traces and the exec-server logs. The server still writes its own logs. |
|
|
74
|
+
| `BMA_STATE_DIR` | `.bma` in `BMA_HOME_DIR` | The directory of `state.json`. |
|
|
75
|
+
| `BMA_HOME_DIR` | The home directory of the image user | `HOME` of the exec-server. |
|
|
76
|
+
| `BMA_CODEX_HOME` | `.codex` in `BMA_HOME_DIR` | `CODEX_HOME` of the exec-server. |
|
|
77
|
+
| `BMA_CODEX_BINARY` | `/opt/bma/bin/codex` | The Codex binary. |
|
|
78
|
+
| `BMA_MAX_TURN_LEASE` | `300` | The longest turn lease, in seconds. The server changes a longer request to this value. |
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# Skills and plugins
|
|
82
|
+
|
|
83
|
+
BMA finds skills and plugins in the directories that the client gives in `capability_directories`. A directory can be
|
|
84
|
+
at any absolute path in the ACR. BMA does not need a copy in the workspace.
|
|
85
|
+
|
|
86
|
+
- To add a skill to the image, put a plugin under `plugins/` and add its path under `/opt/bma/plugins` to
|
|
87
|
+
`CAPABILITY_DIRECTORIES` in `client.py`. A plugin has a `.codex-plugin/plugin.json` file and a `skills/` directory
|
|
88
|
+
with one directory for each skill. Each skill directory has a `SKILL.md` file.
|
|
89
|
+
- To share skills from an S3 bucket, mount an S3 Files access point, for example at `/mnt/skills`, and add that
|
|
90
|
+
path to `CAPABILITY_DIRECTORIES`. Add the mount to `filesystemConfigurations` in `agentcore/agentcore.json` as an
|
|
91
|
+
`s3FilesAccessPoint` entry. An S3 Files mount needs VPC network mode.
|
|
92
|
+
|
|
93
|
+
# Deploy
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
agentcore deploy
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Give the ACR ARN to BMA when you create the BMA environment.
|
|
100
|
+
|
|
101
|
+
# Run the client
|
|
102
|
+
|
|
103
|
+
Run the client from this directory with the ACR ARN from `agentcore status`:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
uv run client.py --runtime <ACR ARN>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`uv run` installs the dependencies and the `dev` group from `pyproject.toml` in `.venv`. The client creates a BMA
|
|
110
|
+
session and prints the session ID, each command with its output, and the answer of the agent as it streams.
|
|
111
|
+
|
|
112
|
+
To send another input to the same session, add the BMA session ID. If the session does not exist, the client creates a
|
|
113
|
+
new session and prints its ID.
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
uv run client.py --runtime <ACR ARN> --session-id <BMA session ID> --input "List the files in the workspace."
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
If you add a Gateway, add `--gateway <Gateway MCP URL>` with the URL from the output of `agentcore deploy`. BMA adds
|
|
120
|
+
the Gateway tools when it creates the session, so the flag has no effect on a session that exists.
|
|
121
|
+
|
|
122
|
+
To delete the session after the turn, add `--delete`. To print each stream event as JSON, add `--raw`.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"Version": "2012-10-17",
|
|
3
|
+
"Statement": [
|
|
4
|
+
{
|
|
5
|
+
"Sid": "AttachToBmaEnvironment",
|
|
6
|
+
"Effect": "Allow",
|
|
7
|
+
"Action": ["bedrock-mantle:RegisterEnvironment", "bedrock-mantle:ConnectEnvironment"],
|
|
8
|
+
"Resource": "arn:*:bedrock-mantle:*:*:project/*"
|
|
9
|
+
}
|
|
10
|
+
]
|
|
11
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""Send an input to a Bedrock Managed Agents session that uses this project's ACR."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from aws_bedrock_token_generator import provide_token
|
|
8
|
+
from openai import NotFoundError, OpenAI
|
|
9
|
+
|
|
10
|
+
BMA_MODEL_ID = "openai.gpt-5.6-luna"
|
|
11
|
+
WORKSPACE_DIRECTORY = "/home/app/workspace"
|
|
12
|
+
CAPABILITY_DIRECTORIES = ["/opt/bma/plugins"]
|
|
13
|
+
TURN_END = ("completed", "failed", "cancelled")
|
|
14
|
+
TOOL_CALLS = ("mcp_call", "function_call", "web_search_call")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def show(data: dict[str, Any]) -> None:
|
|
18
|
+
"""Prints the session ID, the commands, the tool calls, and the answer."""
|
|
19
|
+
kind = data["type"].removeprefix("agent.session.")
|
|
20
|
+
item = data.get("item") or {}
|
|
21
|
+
if kind == "created":
|
|
22
|
+
print(f"Session {data['session']['id']}")
|
|
23
|
+
elif kind == "turn.output_text.delta":
|
|
24
|
+
print(data["delta"], end="", flush=True)
|
|
25
|
+
elif kind == "turn.item.done" and item.get("type") == "command_execution":
|
|
26
|
+
print(f"\n$ {item['command']}\n{item.get('output') or ''}".rstrip())
|
|
27
|
+
elif kind == "turn.item.done" and item.get("type") in TOOL_CALLS:
|
|
28
|
+
print(f"\nTool {item.get('name') or item['type']} {item.get('status')}")
|
|
29
|
+
elif kind == "error" or kind.split(".")[-1] in TURN_END:
|
|
30
|
+
source = data.get("turn") or data.get("environment") or data.get("session")
|
|
31
|
+
print(f"\n{kind} {(source or data).get('error') or ''}".rstrip())
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def main() -> None:
|
|
35
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
36
|
+
parser.add_argument("--runtime", required=True, help="The ACR ARN.")
|
|
37
|
+
parser.add_argument(
|
|
38
|
+
"--session-id",
|
|
39
|
+
help="The BMA session ID. If it does not exist, the client creates a session.",
|
|
40
|
+
)
|
|
41
|
+
parser.add_argument(
|
|
42
|
+
"--input",
|
|
43
|
+
default="Use the acr-report skill to save an ACR report in the workspace.",
|
|
44
|
+
)
|
|
45
|
+
parser.add_argument(
|
|
46
|
+
"--gateway",
|
|
47
|
+
help="The Gateway URL from the output of `agentcore deploy`.",
|
|
48
|
+
)
|
|
49
|
+
parser.add_argument("--delete", action="store_true", help="Delete the session.")
|
|
50
|
+
parser.add_argument("--raw", action="store_true", help="Print events as JSON.")
|
|
51
|
+
args = parser.parse_args()
|
|
52
|
+
# BMA must run in the Region of the ACR.
|
|
53
|
+
region = args.runtime.split(":")[3]
|
|
54
|
+
|
|
55
|
+
with OpenAI(
|
|
56
|
+
api_key=lambda: provide_token(region=region),
|
|
57
|
+
base_url=f"https://bedrock-mantle.{region}.api.aws/openai/v1",
|
|
58
|
+
) as client:
|
|
59
|
+
sessions = client.beta.agents.sessions
|
|
60
|
+
session_id = args.session_id
|
|
61
|
+
if session_id:
|
|
62
|
+
try:
|
|
63
|
+
session = sessions.retrieve(session_id).model_dump(warnings=False)
|
|
64
|
+
except NotFoundError:
|
|
65
|
+
print(f"Session {session_id} does not exist.")
|
|
66
|
+
session_id = None
|
|
67
|
+
else:
|
|
68
|
+
if session["environment"].get("runtime_arn") != args.runtime:
|
|
69
|
+
raise ValueError(f"Session {session_id} uses another ACR.")
|
|
70
|
+
|
|
71
|
+
if session_id:
|
|
72
|
+
# BMA opens the stream only with stream=true, and the SDK does not send it.
|
|
73
|
+
events = sessions.events.stream(session_id, extra_query={"stream": "true"})
|
|
74
|
+
message = {
|
|
75
|
+
"role": "user",
|
|
76
|
+
"content": [{"type": "input_text", "text": args.input}],
|
|
77
|
+
}
|
|
78
|
+
sessions.events.create(
|
|
79
|
+
session_id,
|
|
80
|
+
events=[{"type": "agent.session.input.message", "input": [message]}],
|
|
81
|
+
)
|
|
82
|
+
else:
|
|
83
|
+
agent: dict[str, Any] = {
|
|
84
|
+
"model": BMA_MODEL_ID,
|
|
85
|
+
"instructions": "Use the available tools to complete the task.",
|
|
86
|
+
}
|
|
87
|
+
if args.gateway:
|
|
88
|
+
# Bedrock Managed Agents calls Gateway with IAM from the service side.
|
|
89
|
+
agent["tools"] = [
|
|
90
|
+
{
|
|
91
|
+
"type": "mcp",
|
|
92
|
+
"server_label": "team_tools",
|
|
93
|
+
"required": True,
|
|
94
|
+
"connection_origin": "service",
|
|
95
|
+
"transport": {"type": "http", "server_url": args.gateway},
|
|
96
|
+
}
|
|
97
|
+
]
|
|
98
|
+
args.input += (
|
|
99
|
+
" Then use the Gateway's documentation and runbook tools to explain"
|
|
100
|
+
" how to investigate an MCP connection failure. Cite your sources."
|
|
101
|
+
)
|
|
102
|
+
events = sessions.create(
|
|
103
|
+
agent=agent,
|
|
104
|
+
environment={
|
|
105
|
+
"type": "aws_bedrock_agentcore",
|
|
106
|
+
"runtime_arn": args.runtime,
|
|
107
|
+
"runtime_qualifier": "DEFAULT",
|
|
108
|
+
"workspace_directory": WORKSPACE_DIRECTORY,
|
|
109
|
+
"capability_directories": CAPABILITY_DIRECTORIES,
|
|
110
|
+
},
|
|
111
|
+
input=args.input,
|
|
112
|
+
stream=True,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
with events:
|
|
116
|
+
for event in events:
|
|
117
|
+
data = event.model_dump(mode="json", warnings=False)
|
|
118
|
+
session_id = session_id or (data.get("session") or {}).get("id")
|
|
119
|
+
if args.raw:
|
|
120
|
+
print(json.dumps(data), flush=True)
|
|
121
|
+
else:
|
|
122
|
+
show(data)
|
|
123
|
+
if data["type"].removeprefix("agent.session.turn.") in TURN_END:
|
|
124
|
+
break
|
|
125
|
+
|
|
126
|
+
if args.delete:
|
|
127
|
+
sessions.delete(session_id)
|
|
128
|
+
print(f"\nDeleted session {session_id}")
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
if __name__ == "__main__":
|
|
132
|
+
main()
|