thinkwork-cli 0.8.2 → 0.9.1
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/LICENSE +202 -0
- package/README.md +18 -2
- package/dist/cli.js +3004 -215
- package/dist/terraform/examples/greenfield/main.tf +325 -19
- package/dist/terraform/examples/greenfield/terraform.tfvars.example +14 -0
- package/dist/terraform/modules/app/agentcore-code-interpreter/Dockerfile.sandbox-base +61 -0
- package/dist/terraform/modules/app/agentcore-code-interpreter/README.md +54 -0
- package/dist/terraform/modules/app/agentcore-code-interpreter/main.tf +197 -0
- package/dist/terraform/modules/app/agentcore-code-interpreter/scripts/build_and_push_sandbox_base.sh +70 -0
- package/dist/terraform/modules/app/agentcore-flue/README.md +58 -0
- package/dist/terraform/modules/app/agentcore-flue/main.tf +322 -0
- package/dist/terraform/modules/app/agentcore-flue/outputs.tf +23 -0
- package/dist/terraform/modules/app/agentcore-flue/variables.tf +91 -0
- package/dist/terraform/modules/app/agentcore-memory/scripts/create_or_find_memory.sh +0 -0
- package/dist/terraform/modules/app/agentcore-runtime/main.tf +204 -4
- package/dist/terraform/modules/app/appsync-subscriptions/main.tf +4 -0
- package/dist/terraform/modules/app/appsync-subscriptions/outputs.tf +5 -0
- package/dist/terraform/modules/app/computer-runtime/README.md +15 -0
- package/dist/terraform/modules/app/computer-runtime/main.tf +406 -0
- package/dist/terraform/modules/app/computer-runtime/outputs.tf +75 -0
- package/dist/terraform/modules/app/computer-runtime/variables.tf +66 -0
- package/dist/terraform/modules/app/hindsight-memory/main.tf +6 -0
- package/dist/terraform/modules/app/lambda-api/eval-fanout.tf +128 -0
- package/dist/terraform/modules/app/lambda-api/handlers.tf +1557 -42
- package/dist/terraform/modules/app/lambda-api/main.tf +299 -15
- package/dist/terraform/modules/app/lambda-api/mcp-oauth.tf +118 -0
- package/dist/terraform/modules/app/lambda-api/oauth-secrets.tf +49 -0
- package/dist/terraform/modules/app/lambda-api/outputs.tf +38 -0
- package/dist/terraform/modules/app/lambda-api/slack-app-secrets.tf +43 -0
- package/dist/terraform/modules/app/lambda-api/stripe-secrets.tf +53 -0
- package/dist/terraform/modules/app/lambda-api/variables.tf +349 -2
- package/dist/terraform/modules/app/lambda-api/workspace-events.tf +125 -0
- package/dist/terraform/modules/app/routines-stepfunctions/main.tf +453 -0
- package/dist/terraform/modules/app/sandbox-log-scrubber/README.md +66 -0
- package/dist/terraform/modules/app/sandbox-log-scrubber/main.tf +200 -0
- package/dist/terraform/modules/app/static-site/main.tf +146 -5
- package/dist/terraform/modules/app/www-dns/main.tf +118 -15
- package/dist/terraform/modules/app/www-dns/outputs.tf +10 -0
- package/dist/terraform/modules/app/www-dns/variables.tf +42 -0
- package/dist/terraform/modules/data/aurora-postgres/main.tf +164 -3
- package/dist/terraform/modules/data/aurora-postgres/outputs.tf +34 -0
- package/dist/terraform/modules/data/aurora-postgres/variables.tf +16 -0
- package/dist/terraform/modules/data/compliance-audit-bucket/README.md +145 -0
- package/dist/terraform/modules/data/compliance-audit-bucket/main.tf +573 -0
- package/dist/terraform/modules/data/compliance-audit-bucket/outputs.tf +43 -0
- package/dist/terraform/modules/data/compliance-audit-bucket/variables.tf +93 -0
- package/dist/terraform/modules/data/compliance-exports-bucket/main.tf +269 -0
- package/dist/terraform/modules/data/compliance-exports-bucket/outputs.tf +23 -0
- package/dist/terraform/modules/data/compliance-exports-bucket/variables.tf +50 -0
- package/dist/terraform/modules/data/s3-backups-bucket/main.tf +123 -0
- package/dist/terraform/modules/data/s3-buckets/main.tf +13 -0
- package/dist/terraform/modules/foundation/cognito/variables.tf +5 -2
- package/dist/terraform/modules/thinkwork/main.tf +439 -21
- package/dist/terraform/modules/thinkwork/outputs.tf +121 -0
- package/dist/terraform/modules/thinkwork/variables.tf +165 -6
- package/dist/terraform/schema.graphql +45 -0
- package/package.json +15 -14
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
################################################################################
|
|
2
|
+
# AgentCore Code Interpreter — App Module (stage-level)
|
|
3
|
+
#
|
|
4
|
+
# Stage-scoped artifacts for the AgentCore Code Interpreter sandbox:
|
|
5
|
+
# * ECR repository for the blessed sandbox base image (Python 3.12 +
|
|
6
|
+
# pinned libs + sitecustomize.py R13 scrubber baked in).
|
|
7
|
+
# * Lifecycle policy to trim old images.
|
|
8
|
+
# * Outputs the per-tenant provisioning Lambda (Unit 5) consumes to
|
|
9
|
+
# CreateCodeInterpreter for each tenant on demand.
|
|
10
|
+
#
|
|
11
|
+
# **Per-tenant resources live elsewhere.** AgentCore Code Interpreter
|
|
12
|
+
# instances are created per-tenant by the ``agentcore-admin`` Lambda at
|
|
13
|
+
# tenant-create time — see docs/adrs/per-tenant-aws-resource-fanout.md.
|
|
14
|
+
# This module stops at the stage-level substrate.
|
|
15
|
+
#
|
|
16
|
+
# **Image build.** The Dockerfile.sandbox-base next to this file is built
|
|
17
|
+
# and pushed by a CI job (scripts/build_and_push_sandbox_base.sh) — not by
|
|
18
|
+
# Terraform. Terraform owns the ECR repo and IAM plumbing; the image
|
|
19
|
+
# lifecycle is intentionally owned by CI so a repository bump is a
|
|
20
|
+
# reviewable PR rather than a ``terraform apply`` side-effect.
|
|
21
|
+
################################################################################
|
|
22
|
+
|
|
23
|
+
terraform {
|
|
24
|
+
required_providers {
|
|
25
|
+
aws = {
|
|
26
|
+
source = "hashicorp/aws"
|
|
27
|
+
version = ">= 5.0"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
variable "stage" {
|
|
33
|
+
description = "Deployment stage (dev, prod, etc.) — names the ECR repo and appears in image tags."
|
|
34
|
+
type = string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
variable "region" {
|
|
38
|
+
description = "AWS region."
|
|
39
|
+
type = string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
variable "account_id" {
|
|
43
|
+
description = "AWS account ID (used to construct IAM resource ARNs)."
|
|
44
|
+
type = string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
variable "image_retention_count" {
|
|
48
|
+
description = "Keep the last N image tags in ECR; older ones are lifecycle-expired."
|
|
49
|
+
type = number
|
|
50
|
+
default = 10
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# Environment catalog. Kept as a locals block (not a variable) because v1
|
|
54
|
+
# semantics are fixed in the plan — extending requires a reviewable PR,
|
|
55
|
+
# not a tfvars tweak.
|
|
56
|
+
locals {
|
|
57
|
+
environments = {
|
|
58
|
+
"default-public" = {
|
|
59
|
+
description = "Full public internet outbound; for community-CLI + pip-install workloads."
|
|
60
|
+
network_mode = "PUBLIC"
|
|
61
|
+
}
|
|
62
|
+
"internal-only" = {
|
|
63
|
+
description = "S3 + DNS + AWS service endpoints only; no public egress."
|
|
64
|
+
network_mode = "SANDBOX"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
################################################################################
|
|
70
|
+
# ECR repository — stage-level base image
|
|
71
|
+
################################################################################
|
|
72
|
+
|
|
73
|
+
resource "aws_ecr_repository" "sandbox_base" {
|
|
74
|
+
name = "thinkwork-${var.stage}-sandbox-base"
|
|
75
|
+
image_tag_mutability = "IMMUTABLE"
|
|
76
|
+
force_delete = true
|
|
77
|
+
|
|
78
|
+
image_scanning_configuration {
|
|
79
|
+
scan_on_push = true
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
tags = {
|
|
83
|
+
Name = "thinkwork-${var.stage}-sandbox-base"
|
|
84
|
+
Stage = var.stage
|
|
85
|
+
Purpose = "agentcore-code-interpreter-base-image"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
resource "aws_ecr_lifecycle_policy" "sandbox_base" {
|
|
90
|
+
repository = aws_ecr_repository.sandbox_base.name
|
|
91
|
+
|
|
92
|
+
policy = jsonencode({
|
|
93
|
+
rules = [{
|
|
94
|
+
rulePriority = 1
|
|
95
|
+
description = "Keep last ${var.image_retention_count} images"
|
|
96
|
+
selection = {
|
|
97
|
+
tagStatus = "any"
|
|
98
|
+
countType = "imageCountMoreThan"
|
|
99
|
+
countNumber = var.image_retention_count
|
|
100
|
+
}
|
|
101
|
+
action = {
|
|
102
|
+
type = "expire"
|
|
103
|
+
}
|
|
104
|
+
}]
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
################################################################################
|
|
109
|
+
# IAM policy document — per-tenant trust template
|
|
110
|
+
#
|
|
111
|
+
# Rendered as a JSON string so the agentcore-admin Lambda (Unit 5) can
|
|
112
|
+
# substitute {tenant_id} at CreateRole time. Stored as a terraform output so
|
|
113
|
+
# the Lambda reads it from SSM or environment — not hard-coded in the
|
|
114
|
+
# Lambda source.
|
|
115
|
+
################################################################################
|
|
116
|
+
|
|
117
|
+
locals {
|
|
118
|
+
tenant_role_trust_policy_template = jsonencode({
|
|
119
|
+
Version = "2012-10-17"
|
|
120
|
+
Statement = [{
|
|
121
|
+
Effect = "Allow"
|
|
122
|
+
Principal = { Service = "bedrock-agentcore.amazonaws.com" }
|
|
123
|
+
Action = "sts:AssumeRole"
|
|
124
|
+
Condition = {
|
|
125
|
+
StringEquals = {
|
|
126
|
+
"aws:SourceAccount" = var.account_id
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}]
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
# Inline policy template: tenant-wildcard read on the sandbox SM path
|
|
133
|
+
# family. {tenant_id} is substituted at CreateRole time.
|
|
134
|
+
tenant_role_inline_policy_template = jsonencode({
|
|
135
|
+
Version = "2012-10-17"
|
|
136
|
+
Statement = [
|
|
137
|
+
{
|
|
138
|
+
Sid = "SandboxSecretsRead"
|
|
139
|
+
Effect = "Allow"
|
|
140
|
+
Action = [
|
|
141
|
+
"secretsmanager:GetSecretValue",
|
|
142
|
+
"secretsmanager:DescribeSecret",
|
|
143
|
+
]
|
|
144
|
+
# Wildcard over users within the tenant. See T1b residual.
|
|
145
|
+
Resource = "arn:aws:secretsmanager:${var.region}:${var.account_id}:secret:thinkwork/${var.stage}/sandbox/{tenant_id}/*"
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
Sid = "SandboxCloudWatchLogs"
|
|
149
|
+
Effect = "Allow"
|
|
150
|
+
Action = [
|
|
151
|
+
"logs:CreateLogStream",
|
|
152
|
+
"logs:PutLogEvents",
|
|
153
|
+
]
|
|
154
|
+
Resource = "arn:aws:logs:${var.region}:${var.account_id}:log-group:/aws/bedrock-agentcore/runtimes/*"
|
|
155
|
+
},
|
|
156
|
+
]
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
################################################################################
|
|
161
|
+
# Outputs
|
|
162
|
+
################################################################################
|
|
163
|
+
|
|
164
|
+
output "ecr_repository_name" {
|
|
165
|
+
description = "Name of the sandbox base image ECR repo."
|
|
166
|
+
value = aws_ecr_repository.sandbox_base.name
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
output "ecr_repository_url" {
|
|
170
|
+
description = "Full URL of the sandbox base image ECR repo."
|
|
171
|
+
value = aws_ecr_repository.sandbox_base.repository_url
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
output "environment_ids" {
|
|
175
|
+
description = "Enum of valid sandbox environment identifiers."
|
|
176
|
+
value = keys(local.environments)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
output "environments" {
|
|
180
|
+
description = "Full environment metadata (network mode + description) for each sandbox environment."
|
|
181
|
+
value = local.environments
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
output "tenant_role_trust_policy_template" {
|
|
185
|
+
description = "JSON trust-policy template for per-tenant sandbox IAM roles. Consumer substitutes {tenant_id}."
|
|
186
|
+
value = local.tenant_role_trust_policy_template
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
output "tenant_role_inline_policy_template" {
|
|
190
|
+
description = "JSON inline-policy template for per-tenant sandbox IAM roles. Consumer substitutes {tenant_id}."
|
|
191
|
+
value = local.tenant_role_inline_policy_template
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
output "stage" {
|
|
195
|
+
description = "Echo of the stage variable (convenience for downstream modules)."
|
|
196
|
+
value = var.stage
|
|
197
|
+
}
|
package/dist/terraform/modules/app/agentcore-code-interpreter/scripts/build_and_push_sandbox_base.sh
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
################################################################################
|
|
3
|
+
# build_and_push_sandbox_base.sh — build + push the AgentCore sandbox base
|
|
4
|
+
# image to the stage's ECR repo. Invoked by CI; also safe to run locally
|
|
5
|
+
# once ``thinkwork doctor`` reports green.
|
|
6
|
+
#
|
|
7
|
+
# Usage (from repo root):
|
|
8
|
+
# bash terraform/modules/app/agentcore-code-interpreter/scripts/build_and_push_sandbox_base.sh \
|
|
9
|
+
# --stage dev --region us-east-1
|
|
10
|
+
#
|
|
11
|
+
# The script:
|
|
12
|
+
# 1. Reads the ECR repo URL from ``terraform output -raw`` (must be run
|
|
13
|
+
# after ``terraform apply`` lands the agentcore-code-interpreter
|
|
14
|
+
# module for the target stage).
|
|
15
|
+
# 2. docker build the Dockerfile.sandbox-base with the repo root as
|
|
16
|
+
# context (so the COPY can reach packages/).
|
|
17
|
+
# 3. Tags with the current git SHA (immutable tag per the ECR repo
|
|
18
|
+
# config) and ``:latest``.
|
|
19
|
+
# 4. docker push both tags after ``aws ecr get-login-password``.
|
|
20
|
+
################################################################################
|
|
21
|
+
set -euo pipefail
|
|
22
|
+
|
|
23
|
+
STAGE=""
|
|
24
|
+
REGION=""
|
|
25
|
+
|
|
26
|
+
while [[ $# -gt 0 ]]; do
|
|
27
|
+
case "$1" in
|
|
28
|
+
--stage) STAGE="$2"; shift 2 ;;
|
|
29
|
+
--region) REGION="$2"; shift 2 ;;
|
|
30
|
+
*) echo "unknown arg: $1" >&2; exit 2 ;;
|
|
31
|
+
esac
|
|
32
|
+
done
|
|
33
|
+
|
|
34
|
+
: "${STAGE:?--stage is required}"
|
|
35
|
+
: "${REGION:?--region is required}"
|
|
36
|
+
|
|
37
|
+
# Resolve the repo URL from terraform state. Tolerant: if the caller runs
|
|
38
|
+
# this before the module applies, fall back to the deterministic name.
|
|
39
|
+
REPO_URL="$(
|
|
40
|
+
terraform -chdir="terraform/examples/greenfield" output -raw \
|
|
41
|
+
sandbox_base_ecr_repository_url 2>/dev/null \
|
|
42
|
+
|| echo ""
|
|
43
|
+
)"
|
|
44
|
+
if [[ -z "$REPO_URL" ]]; then
|
|
45
|
+
ACCOUNT_ID="$(aws sts get-caller-identity --query Account --output text)"
|
|
46
|
+
REPO_URL="${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/thinkwork-${STAGE}-sandbox-base"
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
GIT_SHA="$(git rev-parse --short HEAD)"
|
|
50
|
+
IMAGE_TAG="${GIT_SHA}"
|
|
51
|
+
|
|
52
|
+
echo "[sandbox-base] repo: ${REPO_URL}"
|
|
53
|
+
echo "[sandbox-base] tag: ${IMAGE_TAG}"
|
|
54
|
+
|
|
55
|
+
# ECR login
|
|
56
|
+
aws ecr get-login-password --region "${REGION}" \
|
|
57
|
+
| docker login --username AWS --password-stdin "${REPO_URL%/*}"
|
|
58
|
+
|
|
59
|
+
# Build with repo root as the context so COPY can reach packages/.
|
|
60
|
+
docker build \
|
|
61
|
+
--platform=linux/amd64 \
|
|
62
|
+
-f terraform/modules/app/agentcore-code-interpreter/Dockerfile.sandbox-base \
|
|
63
|
+
-t "${REPO_URL}:${IMAGE_TAG}" \
|
|
64
|
+
-t "${REPO_URL}:latest" \
|
|
65
|
+
.
|
|
66
|
+
|
|
67
|
+
docker push "${REPO_URL}:${IMAGE_TAG}"
|
|
68
|
+
docker push "${REPO_URL}:latest"
|
|
69
|
+
|
|
70
|
+
echo "[sandbox-base] pushed ${REPO_URL}:${IMAGE_TAG}"
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# `agentcore-flue` — Flue agent runtime
|
|
2
|
+
|
|
3
|
+
Provisions the **Flue** agent runtime as a Lambda+LWA function (Plan §005 U2).
|
|
4
|
+
|
|
5
|
+
The Flue runtime supersedes the in-flight `agentcore-pi` scaffolding (renamed in U1, PR #785). U2 splits the Flue Lambda + log group + IAM role + event-invoke config out of the Strands `agentcore-runtime` module into this dedicated module so Flue can carry its own permissions surface independently.
|
|
6
|
+
|
|
7
|
+
## Resources owned
|
|
8
|
+
|
|
9
|
+
- `aws_iam_role.agentcore_flue` (`thinkwork-${stage}-agentcore-flue-role`) — assumed by Lambda + Bedrock AgentCore Runtime principals.
|
|
10
|
+
- `aws_iam_role_policy.agentcore_flue` — baseline permissions (S3 skill catalog, Bedrock model invoke, AgentCore Memory + Code Interpreter, CloudWatch Logs, X-Ray ingestion, ECR pull, SSM parameter access, memory-retain Lambda invoke). Forward-compat additions for U4-U8: Aurora Data API + Secrets Manager scoped to `thinkwork-${stage}-*`.
|
|
11
|
+
- `aws_iam_role_policy.agentcore_flue_dlq_send` — `sqs:SendMessage` against the shared async DLQ (injected via `var.async_dlq_arn`).
|
|
12
|
+
- `aws_cloudwatch_log_group.agentcore_flue` (`/thinkwork/${stage}/agentcore-flue`).
|
|
13
|
+
- `aws_lambda_function.agentcore_flue` (`thinkwork-${stage}-agentcore-flue`) — `package_type = "Image"`, pulls `${ecr_repository_url}:flue-latest`.
|
|
14
|
+
- `aws_lambda_function_event_invoke_config.agentcore_flue` — `MaximumRetryAttempts=0`, on-failure → shared DLQ.
|
|
15
|
+
|
|
16
|
+
## Resources NOT owned (injected via inputs)
|
|
17
|
+
|
|
18
|
+
- **ECR repository** — shared with the Strands runtime (`thinkwork-${stage}-agentcore`). The Flue runtime pulls the `flue-latest` / `${sha}-flue` image tags from this repo. Owned by `../agentcore-runtime`; URL injected via `var.ecr_repository_url`.
|
|
19
|
+
- **Async DLQ** — shared with the Strands runtime so operator inspection has a single queue. Owned by `../agentcore-runtime`; ARN injected via `var.async_dlq_arn`.
|
|
20
|
+
|
|
21
|
+
## State migration (U1 → U2)
|
|
22
|
+
|
|
23
|
+
The Flue resources previously lived inside `module.agentcore` under the address `aws_*.agentcore_flue` (renamed from `agentcore_pi` in U1 via in-module `moved {}` blocks). U2 realigns state across modules via `moved {}` blocks declared in the parent composition (`terraform/modules/thinkwork/main.tf`):
|
|
24
|
+
|
|
25
|
+
```hcl
|
|
26
|
+
moved {
|
|
27
|
+
from = module.agentcore.aws_lambda_function.agentcore_flue
|
|
28
|
+
to = module.agentcore_flue.aws_lambda_function.agentcore_flue
|
|
29
|
+
}
|
|
30
|
+
# (and the analogous moves for the log group + event-invoke config + IAM role)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The Lambda `function_name` attribute is unchanged (`thinkwork-${stage}-agentcore-flue` from U1), so the cross-module migration is pure state-address realignment without destroy+create on the underlying AWS resource.
|
|
34
|
+
|
|
35
|
+
## Inputs
|
|
36
|
+
|
|
37
|
+
| Variable | Required | Purpose |
|
|
38
|
+
|---|---|---|
|
|
39
|
+
| `stage` | yes | Deployment stage (e.g., `dev`, `prod`). |
|
|
40
|
+
| `account_id` | yes | AWS account ID (used in IAM resource ARNs). |
|
|
41
|
+
| `region` | yes | AWS region. |
|
|
42
|
+
| `bucket_name` | yes | Primary S3 bucket for skills + workspace files. |
|
|
43
|
+
| `ecr_repository_url` | yes | Shared ECR repo URL from `module.agentcore.ecr_repository_url`. |
|
|
44
|
+
| `async_dlq_arn` | yes | Shared async DLQ ARN from `module.agentcore.agentcore_async_dlq_arn`. |
|
|
45
|
+
| `hindsight_endpoint` | no | Hindsight API endpoint when enabled; empty disables Hindsight tools. |
|
|
46
|
+
| `agentcore_memory_id` | no | AgentCore Memory resource ID for auto-retention. |
|
|
47
|
+
| `api_endpoint` | no | API Gateway base URL for the `/api/skills/complete` callback. |
|
|
48
|
+
| `api_auth_secret` | no | Service-auth bearer for the same callback. |
|
|
49
|
+
| `memory_engine` | no | `hindsight` or `agentcore`; surfaced as `MEMORY_ENGINE` env var. |
|
|
50
|
+
|
|
51
|
+
## Outputs
|
|
52
|
+
|
|
53
|
+
| Output | Purpose |
|
|
54
|
+
|---|---|
|
|
55
|
+
| `flue_function_name` | Direct SDK invoke target (passed to `chat-agent-invoke` as `AGENTCORE_FLUE_FUNCTION_NAME`). |
|
|
56
|
+
| `flue_function_arn` | Granted to `chat-agent-invoke`'s `lambda:InvokeFunction` policy. |
|
|
57
|
+
| `flue_runtime_role_arn` | Assumed by the Bedrock AgentCore Runtime when Flue is invoked via the Bedrock control plane. |
|
|
58
|
+
| `flue_log_group_name` | Scrubber + operator inspection target. |
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
################################################################################
|
|
2
|
+
# AgentCore Flue — App Module
|
|
3
|
+
#
|
|
4
|
+
# Plan §005 U2 — provisions the Flue agent runtime as a Lambda+LWA function.
|
|
5
|
+
#
|
|
6
|
+
# Layout note: this module owns the IAM role, log group, Lambda function, and
|
|
7
|
+
# event-invoke config that are unique to Flue. The shared ECR repo and async
|
|
8
|
+
# DLQ live in `../agentcore-runtime` and are injected via input variables; the
|
|
9
|
+
# IAM policy here grants `sqs:SendMessage` against the shared DLQ ARN.
|
|
10
|
+
#
|
|
11
|
+
# State migration: the resources here previously lived inside `module.agentcore`
|
|
12
|
+
# (the Strands runtime module) under the address `aws_*.agentcore_flue`. The
|
|
13
|
+
# `moved {}` blocks in `terraform/modules/thinkwork/main.tf` realign state
|
|
14
|
+
# across modules without destroy+create on the underlying AWS resources.
|
|
15
|
+
#
|
|
16
|
+
# Forward compat: U4-U8 will tighten the IAM role with Aurora Data API
|
|
17
|
+
# permissions for SessionStore, Secrets Manager for resolved DB credentials,
|
|
18
|
+
# and the AgentCore Code Interpreter actions that the FR-9a spike exercised.
|
|
19
|
+
# U2 lays the role down with the minimum permissions Flue needs to boot —
|
|
20
|
+
# subsequent units extend it as their dependencies land.
|
|
21
|
+
################################################################################
|
|
22
|
+
|
|
23
|
+
# memory-retain Lambda name + ARN are constructed locally rather than
|
|
24
|
+
# taken as inputs to avoid a circular dependency: the lambda-api module
|
|
25
|
+
# already consumes this module's output (agentcore_flue_function_name/arn).
|
|
26
|
+
# Mirrors the pattern in `../agentcore-runtime/main.tf`.
|
|
27
|
+
locals {
|
|
28
|
+
memory_retain_fn_name = "thinkwork-${var.stage}-api-memory-retain"
|
|
29
|
+
memory_retain_fn_arn = "arn:aws:lambda:${var.region}:${var.account_id}:function:${local.memory_retain_fn_name}"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
################################################################################
|
|
33
|
+
# Execution Role
|
|
34
|
+
################################################################################
|
|
35
|
+
|
|
36
|
+
resource "aws_iam_role" "agentcore_flue" {
|
|
37
|
+
name = "thinkwork-${var.stage}-agentcore-flue-role"
|
|
38
|
+
|
|
39
|
+
assume_role_policy = jsonencode({
|
|
40
|
+
Version = "2012-10-17"
|
|
41
|
+
Statement = [{
|
|
42
|
+
Effect = "Allow"
|
|
43
|
+
Principal = { Service = ["ecs-tasks.amazonaws.com", "lambda.amazonaws.com", "bedrock-agentcore.amazonaws.com"] }
|
|
44
|
+
Action = "sts:AssumeRole"
|
|
45
|
+
}]
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
tags = {
|
|
49
|
+
Name = "thinkwork-${var.stage}-agentcore-flue-role"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
resource "aws_iam_role_policy" "agentcore_flue" {
|
|
54
|
+
# Sibling policy: ../agentcore-runtime/main.tf `aws_iam_role_policy.agentcore`.
|
|
55
|
+
# The two policies share ~83% of statements (S3, Bedrock, AgentCore Memory,
|
|
56
|
+
# Code Interpreter, Logs, X-Ray, ECR, SSM, MemoryRetain). Flue adds Aurora
|
|
57
|
+
# Data API + Secrets Manager for U4 SessionStore. Keep both surfaces in
|
|
58
|
+
# sync for shared statements; let Flue-only additions diverge here.
|
|
59
|
+
name = "agentcore-flue-permissions"
|
|
60
|
+
role = aws_iam_role.agentcore_flue.id
|
|
61
|
+
|
|
62
|
+
policy = jsonencode({
|
|
63
|
+
Version = "2012-10-17"
|
|
64
|
+
Statement = [
|
|
65
|
+
{
|
|
66
|
+
Sid = "S3Access"
|
|
67
|
+
Effect = "Allow"
|
|
68
|
+
Action = ["s3:GetObject", "s3:PutObject", "s3:ListBucket"]
|
|
69
|
+
Resource = [
|
|
70
|
+
"arn:aws:s3:::${var.bucket_name}",
|
|
71
|
+
"arn:aws:s3:::${var.bucket_name}/*",
|
|
72
|
+
]
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
# Bedrock invoke spans foundation models, inference profiles, and
|
|
76
|
+
# cross-region routing. The original
|
|
77
|
+
# `arn:aws:bedrock:${region}::foundation-model/*` only covered
|
|
78
|
+
# foundation models in the primary region — but a
|
|
79
|
+
# `us.anthropic.claude-sonnet-...` inference profile (the default
|
|
80
|
+
# routing path used by chat-agent-invoke) lives at
|
|
81
|
+
# `arn:aws:bedrock:${region}:${account}:inference-profile/*` AND
|
|
82
|
+
# dispatches the actual InvokeModel call to the foundation model
|
|
83
|
+
# in whatever region the profile resolves to (us-east-1,
|
|
84
|
+
# us-east-2, us-west-2). The narrow ARN caused every agent turn
|
|
85
|
+
# to fail with AccessDenied silently inside pi-ai's Bedrock
|
|
86
|
+
# provider, surfacing as an empty assistant message with zero
|
|
87
|
+
# token usage. Strands uses `Resource = "*"` for the same actions;
|
|
88
|
+
# we match that posture.
|
|
89
|
+
Sid = "BedrockInvoke"
|
|
90
|
+
Effect = "Allow"
|
|
91
|
+
Action = ["bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream", "bedrock:InvokeAgent"]
|
|
92
|
+
Resource = "*"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
# Automatic memory retention — every agent turn calls CreateEvent
|
|
96
|
+
# to feed AgentCore's background strategies. Also needs read access
|
|
97
|
+
# so the recall() tool can fetch previously extracted records and
|
|
98
|
+
# so forget() can soft-archive old records. Mirrors the Strands role.
|
|
99
|
+
Sid = "AgentCoreMemoryReadWrite"
|
|
100
|
+
Effect = "Allow"
|
|
101
|
+
Action = [
|
|
102
|
+
"bedrock-agentcore:CreateEvent",
|
|
103
|
+
"bedrock-agentcore:ListEvents",
|
|
104
|
+
"bedrock-agentcore:GetEvent",
|
|
105
|
+
"bedrock-agentcore:ListMemoryRecords",
|
|
106
|
+
"bedrock-agentcore:RetrieveMemoryRecords",
|
|
107
|
+
"bedrock-agentcore:GetMemoryRecord",
|
|
108
|
+
"bedrock-agentcore:BatchCreateMemoryRecords",
|
|
109
|
+
"bedrock-agentcore:BatchUpdateMemoryRecords",
|
|
110
|
+
]
|
|
111
|
+
Resource = "*"
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
# AgentCore Code Interpreter — Flue's primary sandbox per the FR-9a
|
|
115
|
+
# integration spike (`packages/flue-aws/connectors/agentcore-
|
|
116
|
+
# codeinterpreter.ts`). Per-tenant interpreters live under
|
|
117
|
+
# `code-interpreter-custom/*`; sessions are started, exec'd against,
|
|
118
|
+
# and stopped per Flue invocation.
|
|
119
|
+
Sid = "AgentCoreCodeInterpreter"
|
|
120
|
+
Effect = "Allow"
|
|
121
|
+
Action = [
|
|
122
|
+
"bedrock-agentcore:StartCodeInterpreterSession",
|
|
123
|
+
"bedrock-agentcore:StopCodeInterpreterSession",
|
|
124
|
+
"bedrock-agentcore:InvokeCodeInterpreter",
|
|
125
|
+
"bedrock-agentcore:GetCodeInterpreterSession",
|
|
126
|
+
"bedrock-agentcore:ListCodeInterpreterSessions",
|
|
127
|
+
"bedrock-agentcore:GetCodeInterpreter",
|
|
128
|
+
]
|
|
129
|
+
Resource = "arn:aws:bedrock-agentcore:${var.region}:${var.account_id}:code-interpreter-custom/*"
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
Sid = "CloudWatchLogs"
|
|
133
|
+
Effect = "Allow"
|
|
134
|
+
Action = [
|
|
135
|
+
"logs:CreateLogGroup",
|
|
136
|
+
"logs:CreateLogStream",
|
|
137
|
+
"logs:DescribeLogGroups",
|
|
138
|
+
"logs:DescribeLogStreams",
|
|
139
|
+
"logs:PutLogEvents",
|
|
140
|
+
]
|
|
141
|
+
# Lambda log group + AgentCore Runtime container log groups + the
|
|
142
|
+
# account-wide aws/spans log group (CloudWatch Transaction Search
|
|
143
|
+
# destination — required for AgentCore Evaluations to read spans).
|
|
144
|
+
Resource = [
|
|
145
|
+
"arn:aws:logs:${var.region}:${var.account_id}:log-group:/aws/lambda/thinkwork-${var.stage}-*",
|
|
146
|
+
"arn:aws:logs:${var.region}:${var.account_id}:log-group:/aws/lambda/thinkwork-${var.stage}-*:*",
|
|
147
|
+
"arn:aws:logs:${var.region}:${var.account_id}:log-group:/aws/bedrock-agentcore/runtimes/*",
|
|
148
|
+
"arn:aws:logs:${var.region}:${var.account_id}:log-group:/aws/bedrock-agentcore/runtimes/*:*",
|
|
149
|
+
"arn:aws:logs:${var.region}:${var.account_id}:log-group:aws/spans",
|
|
150
|
+
"arn:aws:logs:${var.region}:${var.account_id}:log-group:aws/spans:*",
|
|
151
|
+
]
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
# X-Ray ingestion — ADOT exporters publish spans here, which then
|
|
155
|
+
# flow to aws/spans via the Transaction Search policy. AgentCore
|
|
156
|
+
# Evaluations queries those spans by session.id when scoring runs.
|
|
157
|
+
Sid = "XRayIngest"
|
|
158
|
+
Effect = "Allow"
|
|
159
|
+
Action = [
|
|
160
|
+
"xray:PutTraceSegments",
|
|
161
|
+
"xray:PutTelemetryRecords",
|
|
162
|
+
"xray:GetSamplingRules",
|
|
163
|
+
"xray:GetSamplingTargets",
|
|
164
|
+
]
|
|
165
|
+
Resource = [
|
|
166
|
+
"arn:aws:xray:${var.region}:${var.account_id}:*",
|
|
167
|
+
"*",
|
|
168
|
+
]
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
Sid = "ECRPull"
|
|
172
|
+
Effect = "Allow"
|
|
173
|
+
Action = ["ecr:GetDownloadUrlForLayer", "ecr:BatchGetImage"]
|
|
174
|
+
Resource = "arn:aws:ecr:${var.region}:${var.account_id}:repository/thinkwork-${var.stage}-*"
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
Sid = "ECRAuth"
|
|
178
|
+
Effect = "Allow"
|
|
179
|
+
Action = ["ecr:GetAuthorizationToken"]
|
|
180
|
+
Resource = "*"
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
Sid = "SSMParameterAccess"
|
|
184
|
+
Effect = "Allow"
|
|
185
|
+
Action = ["ssm:GetParameter", "ssm:PutParameter"]
|
|
186
|
+
Resource = "arn:aws:ssm:${var.region}:${var.account_id}:parameter/thinkwork/${var.stage}/agentcore/*"
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
# Async-invoke the memory-retain Lambda after every chat turn so
|
|
190
|
+
# the API's normalized memory layer can run the active engine's
|
|
191
|
+
# retainTurn() path (Hindsight POST /memories or AgentCore
|
|
192
|
+
# CreateEvent). InvocationType=Event from the runtime; this Lambda
|
|
193
|
+
# is the only target.
|
|
194
|
+
Sid = "MemoryRetainInvoke"
|
|
195
|
+
Effect = "Allow"
|
|
196
|
+
Action = ["lambda:InvokeFunction"]
|
|
197
|
+
Resource = local.memory_retain_fn_arn
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
# Aurora Data API — U4 SessionStore writes thread/message rows via
|
|
201
|
+
# the RDS Data API rather than long-lived Postgres connections.
|
|
202
|
+
# The cluster ARN and credentials secret are wired through the API
|
|
203
|
+
# Lambda's env (Plan §005 U4); listed here as broad cluster scope
|
|
204
|
+
# because the cluster ARN isn't an input to this module yet.
|
|
205
|
+
Sid = "AuroraDataAPI"
|
|
206
|
+
Effect = "Allow"
|
|
207
|
+
Action = [
|
|
208
|
+
"rds-data:ExecuteStatement",
|
|
209
|
+
"rds-data:BatchExecuteStatement",
|
|
210
|
+
"rds-data:BeginTransaction",
|
|
211
|
+
"rds-data:CommitTransaction",
|
|
212
|
+
"rds-data:RollbackTransaction",
|
|
213
|
+
]
|
|
214
|
+
Resource = "arn:aws:rds:${var.region}:${var.account_id}:cluster:thinkwork-${var.stage}-db-*"
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
# Secrets Manager — Flue resolves db credentials and other runtime
|
|
218
|
+
# secrets at invocation time per `feedback_completion_callback_
|
|
219
|
+
# snapshot_pattern`. Scoped to `/thinkwork/${stage}/*` per the
|
|
220
|
+
# existing convention.
|
|
221
|
+
Sid = "SecretsManagerRead"
|
|
222
|
+
Effect = "Allow"
|
|
223
|
+
Action = [
|
|
224
|
+
"secretsmanager:GetSecretValue",
|
|
225
|
+
"secretsmanager:DescribeSecret",
|
|
226
|
+
]
|
|
227
|
+
Resource = "arn:aws:secretsmanager:${var.region}:${var.account_id}:secret:thinkwork-${var.stage}-*"
|
|
228
|
+
},
|
|
229
|
+
]
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
resource "aws_iam_role_policy" "agentcore_flue_dlq_send" {
|
|
234
|
+
name = "agentcore-flue-dlq-send"
|
|
235
|
+
role = aws_iam_role.agentcore_flue.id
|
|
236
|
+
|
|
237
|
+
policy = jsonencode({
|
|
238
|
+
Version = "2012-10-17"
|
|
239
|
+
Statement = [{
|
|
240
|
+
Effect = "Allow"
|
|
241
|
+
Action = ["sqs:SendMessage"]
|
|
242
|
+
Resource = var.async_dlq_arn
|
|
243
|
+
}]
|
|
244
|
+
})
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
################################################################################
|
|
248
|
+
# CloudWatch Log Group
|
|
249
|
+
################################################################################
|
|
250
|
+
|
|
251
|
+
resource "aws_cloudwatch_log_group" "agentcore_flue" {
|
|
252
|
+
name = "/thinkwork/${var.stage}/agentcore-flue"
|
|
253
|
+
retention_in_days = 30
|
|
254
|
+
|
|
255
|
+
tags = {
|
|
256
|
+
Name = "thinkwork-${var.stage}-agentcore-flue-logs"
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
################################################################################
|
|
261
|
+
# Lambda Container Image
|
|
262
|
+
################################################################################
|
|
263
|
+
|
|
264
|
+
resource "aws_lambda_function" "agentcore_flue" {
|
|
265
|
+
function_name = "thinkwork-${var.stage}-agentcore-flue"
|
|
266
|
+
role = aws_iam_role.agentcore_flue.arn
|
|
267
|
+
package_type = "Image"
|
|
268
|
+
image_uri = "${var.ecr_repository_url}:flue-latest"
|
|
269
|
+
timeout = 900
|
|
270
|
+
memory_size = 2048
|
|
271
|
+
|
|
272
|
+
environment {
|
|
273
|
+
variables = {
|
|
274
|
+
PORT = "8080"
|
|
275
|
+
AWS_LWA_PORT = "8080"
|
|
276
|
+
AGENTCORE_MEMORY_ID = var.agentcore_memory_id
|
|
277
|
+
AGENTCORE_FILES_BUCKET = var.bucket_name
|
|
278
|
+
MEMORY_ENGINE = var.memory_engine
|
|
279
|
+
MEMORY_RETAIN_FN_NAME = local.memory_retain_fn_name
|
|
280
|
+
HINDSIGHT_ENDPOINT = var.hindsight_endpoint
|
|
281
|
+
THINKWORK_API_URL = var.api_endpoint
|
|
282
|
+
API_AUTH_SECRET = var.api_auth_secret
|
|
283
|
+
# Plan §005 U4 — AuroraSessionStore uses the RDS Data API to persist
|
|
284
|
+
# Flue's SessionData blobs against threads.session_data. Empty during
|
|
285
|
+
# the first greenfield apply (DB cluster doesn't exist yet); the
|
|
286
|
+
# constructor fail-closes if either is missing at runtime.
|
|
287
|
+
DB_CLUSTER_ARN = var.db_cluster_arn
|
|
288
|
+
DB_SECRET_ARN = var.db_secret_arn
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
logging_config {
|
|
293
|
+
log_group = aws_cloudwatch_log_group.agentcore_flue.name
|
|
294
|
+
log_format = "Text"
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
tags = {
|
|
298
|
+
Name = "thinkwork-${var.stage}-agentcore-flue"
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
################################################################################
|
|
303
|
+
# Async-invoke hardening — MaximumRetryAttempts=0 + DLQ
|
|
304
|
+
#
|
|
305
|
+
# Mirrors the Strands runtime's invoke-config in `../agentcore-runtime/main.tf`.
|
|
306
|
+
# AWS Lambda async-invoke defaults to 2 retries; the agent loop is not
|
|
307
|
+
# idempotent (Bedrock tokens get re-burned, partial deliverables can
|
|
308
|
+
# overwrite the first), so retries are disabled and failed invokes land in
|
|
309
|
+
# the shared DLQ for operator visibility.
|
|
310
|
+
################################################################################
|
|
311
|
+
|
|
312
|
+
resource "aws_lambda_function_event_invoke_config" "agentcore_flue" {
|
|
313
|
+
function_name = aws_lambda_function.agentcore_flue.function_name
|
|
314
|
+
maximum_retry_attempts = 0
|
|
315
|
+
maximum_event_age_in_seconds = 3600
|
|
316
|
+
|
|
317
|
+
destination_config {
|
|
318
|
+
on_failure {
|
|
319
|
+
destination = var.async_dlq_arn
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
################################################################################
|
|
2
|
+
# AgentCore Flue — App Module (outputs)
|
|
3
|
+
################################################################################
|
|
4
|
+
|
|
5
|
+
output "agentcore_flue_function_name" {
|
|
6
|
+
description = "Flue AgentCore Lambda function name (for direct SDK invoke from chat-agent-invoke)"
|
|
7
|
+
value = aws_lambda_function.agentcore_flue.function_name
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
output "agentcore_flue_function_arn" {
|
|
11
|
+
description = "Flue AgentCore Lambda function ARN (for IAM policy on callers; used to grant lambda:InvokeFunction)"
|
|
12
|
+
value = aws_lambda_function.agentcore_flue.arn
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
output "agentcore_flue_runtime_role_arn" {
|
|
16
|
+
description = "IAM role ARN for the Flue agent runtime (assumed by Lambda + Bedrock AgentCore Runtime principals)"
|
|
17
|
+
value = aws_iam_role.agentcore_flue.arn
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
output "agentcore_flue_log_group_name" {
|
|
21
|
+
description = "CloudWatch log group name for the Flue Lambda. Useful for log scrubbing and operator inspection."
|
|
22
|
+
value = aws_cloudwatch_log_group.agentcore_flue.name
|
|
23
|
+
}
|