thinkwork-cli 0.9.0 → 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.
Files changed (57) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +2 -2
  3. package/dist/cli.js +1187 -315
  4. package/dist/terraform/examples/greenfield/main.tf +325 -19
  5. package/dist/terraform/examples/greenfield/terraform.tfvars.example +14 -0
  6. package/dist/terraform/modules/app/agentcore-code-interpreter/Dockerfile.sandbox-base +61 -0
  7. package/dist/terraform/modules/app/agentcore-code-interpreter/README.md +54 -0
  8. package/dist/terraform/modules/app/agentcore-code-interpreter/main.tf +197 -0
  9. package/dist/terraform/modules/app/agentcore-code-interpreter/scripts/build_and_push_sandbox_base.sh +70 -0
  10. package/dist/terraform/modules/app/agentcore-flue/README.md +58 -0
  11. package/dist/terraform/modules/app/agentcore-flue/main.tf +322 -0
  12. package/dist/terraform/modules/app/agentcore-flue/outputs.tf +23 -0
  13. package/dist/terraform/modules/app/agentcore-flue/variables.tf +91 -0
  14. package/dist/terraform/modules/app/agentcore-memory/scripts/create_or_find_memory.sh +0 -0
  15. package/dist/terraform/modules/app/agentcore-runtime/main.tf +165 -0
  16. package/dist/terraform/modules/app/appsync-subscriptions/main.tf +4 -0
  17. package/dist/terraform/modules/app/appsync-subscriptions/outputs.tf +5 -0
  18. package/dist/terraform/modules/app/computer-runtime/README.md +15 -0
  19. package/dist/terraform/modules/app/computer-runtime/main.tf +406 -0
  20. package/dist/terraform/modules/app/computer-runtime/outputs.tf +75 -0
  21. package/dist/terraform/modules/app/computer-runtime/variables.tf +66 -0
  22. package/dist/terraform/modules/app/hindsight-memory/main.tf +6 -0
  23. package/dist/terraform/modules/app/lambda-api/eval-fanout.tf +128 -0
  24. package/dist/terraform/modules/app/lambda-api/handlers.tf +1454 -43
  25. package/dist/terraform/modules/app/lambda-api/main.tf +221 -12
  26. package/dist/terraform/modules/app/lambda-api/mcp-oauth.tf +118 -0
  27. package/dist/terraform/modules/app/lambda-api/oauth-secrets.tf +49 -0
  28. package/dist/terraform/modules/app/lambda-api/outputs.tf +38 -0
  29. package/dist/terraform/modules/app/lambda-api/slack-app-secrets.tf +43 -0
  30. package/dist/terraform/modules/app/lambda-api/stripe-secrets.tf +53 -0
  31. package/dist/terraform/modules/app/lambda-api/variables.tf +349 -2
  32. package/dist/terraform/modules/app/lambda-api/workspace-events.tf +125 -0
  33. package/dist/terraform/modules/app/routines-stepfunctions/main.tf +453 -0
  34. package/dist/terraform/modules/app/sandbox-log-scrubber/README.md +66 -0
  35. package/dist/terraform/modules/app/sandbox-log-scrubber/main.tf +200 -0
  36. package/dist/terraform/modules/app/static-site/main.tf +146 -5
  37. package/dist/terraform/modules/app/www-dns/main.tf +118 -15
  38. package/dist/terraform/modules/app/www-dns/outputs.tf +10 -0
  39. package/dist/terraform/modules/app/www-dns/variables.tf +42 -0
  40. package/dist/terraform/modules/data/aurora-postgres/main.tf +164 -3
  41. package/dist/terraform/modules/data/aurora-postgres/outputs.tf +34 -0
  42. package/dist/terraform/modules/data/aurora-postgres/variables.tf +16 -0
  43. package/dist/terraform/modules/data/compliance-audit-bucket/README.md +145 -0
  44. package/dist/terraform/modules/data/compliance-audit-bucket/main.tf +573 -0
  45. package/dist/terraform/modules/data/compliance-audit-bucket/outputs.tf +43 -0
  46. package/dist/terraform/modules/data/compliance-audit-bucket/variables.tf +93 -0
  47. package/dist/terraform/modules/data/compliance-exports-bucket/main.tf +269 -0
  48. package/dist/terraform/modules/data/compliance-exports-bucket/outputs.tf +23 -0
  49. package/dist/terraform/modules/data/compliance-exports-bucket/variables.tf +50 -0
  50. package/dist/terraform/modules/data/s3-backups-bucket/main.tf +123 -0
  51. package/dist/terraform/modules/data/s3-buckets/main.tf +13 -0
  52. package/dist/terraform/modules/foundation/cognito/variables.tf +2 -2
  53. package/dist/terraform/modules/thinkwork/main.tf +439 -21
  54. package/dist/terraform/modules/thinkwork/outputs.tf +121 -0
  55. package/dist/terraform/modules/thinkwork/variables.tf +153 -2
  56. package/dist/terraform/schema.graphql +17 -0
  57. 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
+ }
@@ -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
+ }