Interactive shells (terminals)
The InvokeAgentRuntimeCommandShell operation opens a persistent, interactive terminal inside a running AgentCore harness session over WebSocket. The API identifies this harness session with a runtime session ID. Unlike one-shot command execution, an interactive shell maintains its environment variables, working directory, command history, and running processes across inputs.
Use an interactive shell for debugging, iterative development, or long-running processes when you need to preserve shell state across commands.
For SigV4 and SigV4 pre-signed requests, the caller needs bedrock-agentcore:InvokeAgentRuntimeCommandShell permission on the harness. Bearer-token requests must satisfy the harness inbound authorizer instead.
Important
At launch, AgentCore CLI commands and the high-level AgentCore SDK shell helpers don’t support harness targets. Connect to a harness shell through the InvokeAgentRuntimeCommandShell WebSocket API directly, as shown on this page.
How it works
InvokeAgentRuntimeCommandShell establishes a WebSocket connection to an interactive shell process in the microVM that runs your harness session. The connection uses binary frames to stream terminal input and output in both directions.
If the harness uses the default environment, the shell runs in the managed harness environment. If the harness uses a custom container, the shell runs in that container. Harness interactive terminals currently support Bash only.
The connection supports:
| Feature | Description |
|---|---|
|
Persistent state |
Environment variables, working directory, command history, and running processes persist within the same shell. |
|
Reconnection |
Provide the same runtime session ID and |
|
Multiple concurrent shells |
Open up to 10 active shell sessions on one harness session. Detached shells don’t count toward this limit. Each shell has an independent terminal process and shares the session filesystem. |
Prerequisites
-
A harness in
READYstate -
Authorization for the selected authentication method:
bedrock-agentcore:InvokeAgentRuntimeCommandShellIAM permission for SigV4, or a bearer token accepted by the harness inbound authorizer -
A WebSocket client that supports binary frames and either request headers or pre-signed URLs
-
For a custom container, Bash installed at
/bin/bash. Other shells, includingshanddash, aren’t currently supported.
For more information about the complete harness permissions model, see Required IAM permissions for callers.
Connect through the WebSocket API
Open a WebSocket connection to the following URL:
wss://bedrock-agentcore.<region>.amazonaws.com/runtimes/<url-encoded-harness-arn>/ws/shells
Use the harness ARN, not the ARN of its managed Runtime. URL-encode the complete harness ARN before putting it in the path. A harness ARN has the following format:
arn:aws:bedrock-agentcore:<region>:<accountId>:harness/<harnessId>
Send the following values with the WebSocket upgrade request:
| Value | Required | Description |
|---|---|---|
|
Authentication |
Yes |
Sign the request with Signature Version 4 (SigV4), use a SigV4 pre-signed URL, or send the bearer token configured by the harness inbound authorizer in the |
|
|
No |
Targets an existing harness session or creates one with the supplied ID. The value must contain at least 33 characters. If you omit it, the service creates a runtime session ID and returns it in the response. |
|
|
No |
For SigV4 connections, optionally offer |
|
|
No |
Reconnects to an existing shell. Use it with the same runtime session ID that created the shell. |
For a browser client that cannot set an Authorization header, base64url-encode the bearer token and offer both base64UrlBearerAuthorization.<encoded-token> and the base64UrlBearerAuthorization sentinel through Sec-WebSocket-Protocol. Do not also offer v1.command.agentcore.aws.dev on a bearer-token connection.
The 101 Switching Protocols response returns the runtime session ID and shell ID in the X-Amzn-Bedrock-AgentCore-Runtime-Session-Id and X-Amzn-Bedrock-AgentCore-Shell-Id headers. Clients that cannot read WebSocket upgrade response headers can read the shell ID from the first STATUS frame. The frame doesn’t contain the runtime session ID, so these clients must generate and retain a runtime session ID before connecting. When you create a pre-signed URL, include the runtime session ID as a query parameter before signing the URL.
The WebSocket upgrade timeout applies only until the service returns 101 Switching Protocols. Shell and custom-container initialization can continue after the upgrade. Wait for the initial STATUS frame before treating the shell as ready, apply a separate application timeout while waiting, and handle close code 1011 as a shell initialization or server failure.
The following Python example uses a generic WebSocket client and botocore only to sign the upgrade request. It doesn’t use an AgentCore SDK shell helper.
pip install "boto3>=1.34" "websockets>=15"
import asyncio import json import urllib.parse import uuid import botocore.session import websockets from botocore.auth import SigV4Auth from botocore.awsrequest import AWSRequest REGION = "us-west-2" HARNESS_ARN = ( "arn:aws:bedrock-agentcore:us-west-2:123456789012:" "harness/MyHarness-a1b2c3d4e5" ) SESSION_ID = str(uuid.uuid4()) # 36 characters SUBPROTOCOL = "v1.command.agentcore.aws.dev" def signed_headers(url): request = AWSRequest( method="GET", url=url, headers={ "X-Amzn-Bedrock-AgentCore-Runtime-Session-Id": SESSION_ID, }, ) credentials = botocore.session.Session().get_credentials() SigV4Auth(credentials, "bedrock-agentcore", REGION).add_auth(request) return dict(request.headers) async def main(): encoded_arn = urllib.parse.quote(HARNESS_ARN, safe="") host = f"bedrock-agentcore.{REGION}.amazonaws.com" path = f"/runtimes/{encoded_arn}/ws/shells" https_url = f"https://{host}{path}" websocket_url = f"wss://{host}{path}" async with websockets.connect( websocket_url, additional_headers=signed_headers(https_url), subprotocols=[SUBPROTOCOL], open_timeout=330, ) as websocket: shell_id = websocket.response.headers.get( "X-Amzn-Bedrock-AgentCore-Shell-Id" ) print(f"Connected to shell {shell_id}") # Channel 0x00 carries terminal input. await websocket.send(b"\x00echo 'Hello from my harness'\n") await websocket.send(b"\x00exit\n") async for frame in websocket: if isinstance(frame, str) or not frame: continue channel, payload = frame[0], frame[1:] if channel == 0x01: # PTY output (command stdout and stderr) print(payload.decode("utf-8", errors="replace"), end="") elif channel == 0x02: # Service or platform diagnostics print(payload.decode("utf-8", errors="replace"), end="") elif channel == 0x03: # STATUS status = json.loads(payload) shell_id = status.get("metadata", {}).get("shellId", shell_id) print(f"\nStatus: {status}") asyncio.run(main())
Binary frame protocol
Every application-level message is a binary WebSocket frame. The first byte identifies the channel, and the remaining bytes are the payload.
| Channel | Direction | Payload |
|---|---|---|
|
|
Client to service |
Raw terminal input. |
|
|
Service to client |
Raw PTY output. The PTY combines command stdout and stderr on this channel. |
|
|
Service to client |
Service or platform diagnostics. Command stderr is part of the |
|
|
Service to client |
UTF-8 JSON containing connection metadata or the shell exit status. |
|
|
Client to service |
UTF-8 JSON in the form |
|
|
Both directions |
Optional application heartbeat. The service echoes the payload. WebSocket ping frames are also supported. |
|
|
Both directions |
Ends the shell session. |
Text frames aren’t part of the protocol. The service ignores isolated text frames and closes the connection with code 1003 after five consecutive text frames.
The first STATUS frame confirms the connection and includes metadata similar to the following:
{ "kind": "Status", "apiVersion": "v1", "metadata": { "shellId": "my-shell", "reconnected": false }, "status": "Success" }
When the shell exits with a nonzero status, the final STATUS frame includes an ExitCode cause in details.causes.
Reconnect to a shell
Store both the runtime session ID and the shell ID. To reconnect, use the same runtime session ID header and add the URL-encoded shell ID as a query parameter:
wss://bedrock-agentcore.<region>.amazonaws.com/runtimes/<url-encoded-harness-arn>/ws/shells?shellId=<shell-id>
The shell process continues running while the connection is detached. On reconnection, the service replays up to 256 KB of buffered output. The connection STATUS frame sets metadata.reconnected to true and includes metadata.bytesDropped if the detached shell produced more output than the replay buffer could retain.
Opening another connection with the same shell ID replaces the existing connection. The replaced client receives close code 4000.
Common use cases
- Interactive debugging
-
Inspect installed packages, files, processes, and environment configuration in the same environment as the harness agent.
- Custom environment validation
-
For a harness with a custom container, verify installed dependencies and filesystem mounts from inside that container.
- Coding agent terminal access
-
Provide a persistent execution environment for coding agents that need to create files, run tests, inspect output, and iterate.
- Long-running processes
-
Start a process, detach from the WebSocket, and reconnect later to check progress or provide input.
Security considerations
Important
A principal that can open a Harness shell has interactive command access to the harness session. Restrict bedrock-agentcore:InvokeAgentRuntimeCommandShell to trusted principals and specific harness ARNs.
The shell can access customer-provided data, files, and secrets in the selected environment or on mounted filesystems. It can also connect to network destinations that the session can reach. The shell environment uses an allowlist and doesn’t inject AgentCore platform or workload credentials. The shell remains inside the session’s isolated microVM and cannot access another customer’s workload.
AgentCore sends the request ID and connection metadata to the harness Runtime log group in Amazon CloudWatch Logs. The service doesn’t log terminal input or output.
Amazon CloudTrail records InvokeAgentRuntimeCommandShell API calls, including the caller identity, timestamp, source IP address, resolved Runtime resource, and response status. CloudTrail doesn’t log terminal input or output.
For more Harness security guidance, see Security and access controls.
Error handling
The WebSocket upgrade can return the following errors:
- ValidationException
-
The request parameters are invalid. Verify that the runtime session ID contains at least 33 characters, the shell ID is valid, and the feature is available in the target Region. Use the URL-encoded harness ARN in the request path; direct use of the managed Runtime ARN is rejected.
- AccessDeniedException
-
The caller doesn’t have
bedrock-agentcore:InvokeAgentRuntimeCommandShellpermission on the harness. - ResourceNotFoundException
-
The specified harness doesn’t exist. Verify that the URL contains the URL-encoded harness ARN.
- ServiceQuotaExceededException (429)
-
The maximum of 10 active shells is already open. End an active shell or detach it, and then retry.
- RuntimeClientError (424)
-
The runtime couldn’t be reached.
- ThrottlingException
-
The request exceeded the API rate limit. Retry with exponential backoff.
- RetryableConflictException (409)
-
The service is provisioning or tearing down the target session. Retry with short exponential backoff.
Once connected, the following close codes indicate why the connection ended:
| Code | Meaning | Client action |
|---|---|---|
|
|
The shell exited cleanly. |
Treat as normal termination. |
|
|
The client sent five consecutive text frames to the binary-only protocol. |
Switch to binary frames. Don’t automatically reconnect with the same behavior. |
|
|
The connection ended without a close frame because of a network failure or service interruption. |
Reconnect with the stored shell ID. |
|
|
The one-hour connection duration expired, the frame rate exceeded 250 frames per second, or the write buffer overflowed. |
Reconnect for duration expiry. For rate or buffer violations, back off before reconnecting. |
|
|
A frame payload exceeded 64 KB. |
Split the payload into frames smaller than 64 KB, then reconnect. |
|
|
An unexpected server failure occurred. |
Retry with exponential backoff. |
|
|
Another client connected with the same shell ID. |
Don’t automatically reconnect. Inform the user that another client attached to the shell. |
Best practices
-
Generate and retain a unique shell ID for each logical terminal.
-
Retain the runtime session ID with the shell ID. Both values are required for reconnection.
-
Read output promptly so that the connection write buffer doesn’t fill.
-
Split large input into binary frames smaller than 64 KB.
-
Reconnect before or after the one-hour connection limit if the shell must remain available longer.
-
End shells explicitly when they are no longer needed. Detached shells don’t count toward the 10-active-shell limit. The service retains at most 20 active and detached shell entries and evicts the oldest detached shell when necessary.
-
Use an upgrade timeout of at least 330 seconds when a connection can start a cold harness session. Apply a separate timeout while waiting for the initial
STATUSframe after the upgrade.
Quotas and limits
| Limit | Value | Description |
|---|---|---|
|
Maximum frame payload |
64 KB |
Larger frames result in close code |
|
Frame rate |
250 frames per second |
Exceeding the limit results in close code |
|
Maximum connection duration |
1 hour |
Reconnect with the same runtime session ID and shell ID to continue. |
|
Concurrent active shells per harness session |
10 |
New connections are rejected when 10 shells are active. Detached shells don’t count toward this limit. |
|
Retained active and detached shell entries |
20 |
When the limit is reached, the service evicts the oldest detached shell before creating another shell. |
|
Reconnection buffer |
256 KB |
Maximum output replayed after reconnection. |
For complete service limits, see Quotas for Amazon Bedrock AgentCore.
Related topics
-
Environment and filesystem - configure the default environment or a custom container
-
Security and access controls - control access to the harness and its execution environment
-
Observability and cost controls - monitor Harness activity and costs