thinkwork-cli 0.12.17 → 0.13.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/dist/{api-client-JUBCQQDF.js → api-client-4DZ266J5.js} +1 -1
- package/dist/{chunk-STY44FQO.js → chunk-Y2WHJECR.js} +44 -12
- package/dist/cli.js +36715 -12965
- package/dist/drizzle/0227_analyst_reader_role.sql +100 -38
- package/dist/drizzle/0230_analyst_rls.sql +183 -66
- package/dist/drizzle/0232_memory_source_thin_ledger.sql +176 -0
- package/dist/drizzle/0232_message_source_event_id.sql +9 -0
- package/dist/drizzle/0233_msteams_install_and_links.sql +151 -0
- package/dist/drizzle/0233_workflow_memory_stage_tokens.sql +20 -0
- package/dist/drizzle/0234_memory_claim_lifecycle.sql +160 -0
- package/dist/drizzle/0235_workflow_token_execution_claim.sql +34 -0
- package/dist/drizzle/0236_memory_retraction_fencing.sql +50 -0
- package/dist/drizzle/0237_memory_claims_active_value_uidx.sql +172 -0
- package/dist/drizzle/0238_memory_erase_generation_fence.sql +97 -0
- package/dist/drizzle/0239_canonical_entity_identity.sql +257 -0
- package/dist/drizzle/0240_memory_personal_scope.sql +45 -0
- package/dist/drizzle/0241_knowledge_base_document_manifest.sql +67 -0
- package/dist/drizzle/0242_search_queries.sql +28 -0
- package/dist/drizzle/0243_section_sources_thread_refs.sql +12 -0
- package/dist/drizzle/0244_system_automations_and_memory_stage_overrides.sql +45 -0
- package/dist/drizzle/0245_wiki_page_render.sql +21 -0
- package/dist/drizzle/0246_automation_workflow_ownership.sql +30 -0
- package/dist/drizzle/0247_capability_runtime.sql +402 -0
- package/dist/drizzle/0248_capability_private_interpreter.sql +13 -0
- package/dist/drizzle/0249_capability_headless_execution_evidence.sql +40 -0
- package/dist/drizzle/0250_kg_schema_extraction.sql +549 -0
- package/dist/drizzle/0251_agents_json_render_ui.sql +25 -0
- package/dist/drizzle/0251_brain_stragglers.sql +242 -0
- package/dist/drizzle/0252_drop_kg_and_straggler_compat_views.sql +100 -0
- package/dist/drizzle/0253_drop_brain_substrate.sql +91 -0
- package/dist/drizzle/0254_capability_autonomous_admission.sql +29 -0
- package/dist/drizzle/0255_capability_autonomous_approval_mode.sql +16 -0
- package/dist/drizzle/0256_agent_profile_manifest_authority.sql +18 -0
- package/dist/drizzle/0257_capability_approvals.sql +53 -0
- package/dist/drizzle/0258_pending_tool_approvals.sql +84 -0
- package/dist/drizzle/0259_tenant_capability_registry_trust.sql +19 -0
- package/dist/drizzle/0260_scheduled_jobs_run_as_user.sql +20 -0
- package/dist/drizzle/0261_thread_turns_origin_turn_index.sql +11 -0
- package/dist/terraform/examples/greenfield/main.tf +40 -0
- package/dist/terraform/modules/app/agentcore-admin/main.tf +341 -0
- package/dist/terraform/modules/app/agentcore-admin/outputs.tf +20 -0
- package/dist/terraform/modules/app/agentcore-code-interpreter/main.tf +61 -0
- package/dist/terraform/modules/app/agentcore-harness/main.tf +101 -0
- package/dist/terraform/modules/app/agentcore-harness/outputs.tf +9 -0
- package/dist/terraform/modules/app/agentcore-harness/variables.tf +25 -0
- package/dist/terraform/modules/app/agentcore-pi/main.tf +7 -0
- package/dist/terraform/modules/app/capability-broker/main.tf +393 -0
- package/dist/terraform/modules/app/capability-broker/outputs.tf +54 -0
- package/dist/terraform/modules/app/capability-broker/variables.tf +98 -0
- package/dist/terraform/modules/app/deployment-control-plane/runner.py +46 -5
- package/dist/terraform/modules/app/deployment-control-plane/test_runner_bundle.py +94 -0
- package/dist/terraform/modules/app/lambda-api/capability-search-alarms.tf +60 -0
- package/dist/terraform/modules/app/lambda-api/handlers.tf +361 -16
- package/dist/terraform/modules/app/lambda-api/iam-grouped.tf +131 -5
- package/dist/terraform/modules/app/lambda-api/main.tf +11 -0
- package/dist/terraform/modules/app/lambda-api/memory-alarms.tf +92 -0
- package/dist/terraform/modules/app/lambda-api/msteams-app-secrets.tf +46 -0
- package/dist/terraform/modules/app/lambda-api/variables.tf +92 -0
- package/dist/terraform/modules/app/workflow-interpreter-stepfunctions/alarms.tf +47 -0
- package/dist/terraform/modules/app/workflow-interpreter-stepfunctions/main.tf +43 -2
- package/dist/terraform/modules/foundation/cognito/main.tf +32 -8
- package/dist/terraform/modules/foundation/cognito/variables.tf +12 -0
- package/dist/terraform/modules/foundation/vpc/outputs.tf +5 -0
- package/dist/terraform/modules/thinkwork/main.tf +152 -5
- package/dist/terraform/modules/thinkwork/outputs.tf +55 -0
- package/dist/terraform/modules/thinkwork/variables.tf +42 -0
- package/dist/workspace-defaults/files/TOOLS.md +39 -0
- package/dist/workspace-defaults/files/agents/analyst/INSTRUCTIONS.md +18 -0
- package/dist/workspace-defaults/files/agents/coding/INSTRUCTIONS.md +11 -0
- package/dist/workspace-defaults/files/agents/research/INSTRUCTIONS.md +12 -0
- package/dist/workspace-defaults/files/agents/reviewer/INSTRUCTIONS.md +15 -0
- package/dist/workspace-defaults/files/skills/document-composer/SKILL.md +6 -2
- package/package.json +4 -2
- /package/dist/workspace-defaults/files/{AGENTS.md → INSTRUCTIONS.md} +0 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
-- THINK-302 U7 (R28/KTD-14): run-as identity for wakeup/scheduled turns.
|
|
2
|
+
--
|
|
3
|
+
-- The user a scheduled/wakeup turn runs AS, so it composes that user's +
|
|
4
|
+
-- the run-in-space's capability scopes. A job with no run_as_user_id
|
|
5
|
+
-- compiles root + sub-agent only — it NEVER inherits the creator's grants.
|
|
6
|
+
-- Set/cleared only through an authorized surface (KTD-14 save-time authz);
|
|
7
|
+
-- dispatch-time revalidation drops stale grants.
|
|
8
|
+
--
|
|
9
|
+
-- Additive, idempotent nullable-FK column add (ON DELETE SET NULL so a
|
|
10
|
+
-- deactivated user's jobs degrade to root-only rather than break).
|
|
11
|
+
-- Hand-rolled only to carry the drift-gate marker; db:push would also add it.
|
|
12
|
+
--
|
|
13
|
+
-- Apply manually:
|
|
14
|
+
-- psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -f drizzle/0260_scheduled_jobs_run_as_user.sql
|
|
15
|
+
-- creates-column: public.scheduled_jobs.run_as_user_id
|
|
16
|
+
|
|
17
|
+
\set ON_ERROR_STOP on
|
|
18
|
+
|
|
19
|
+
ALTER TABLE public.scheduled_jobs
|
|
20
|
+
ADD COLUMN IF NOT EXISTS run_as_user_id uuid REFERENCES public.users(id) ON DELETE SET NULL;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
-- THINK-307 KTD4: the retry dispatcher's successor-turn guard, THINK-308's
|
|
2
|
+
-- finalize reconciliation, and THINK-309's recovery resolver all look up
|
|
3
|
+
-- thread_turns by origin_turn_id; no index covers that column today.
|
|
4
|
+
-- Additive — safe to apply ahead of code deploy.
|
|
5
|
+
--
|
|
6
|
+
-- Hand-rolled (drizzle meta journal is not in use for this change);
|
|
7
|
+
-- apply with: psql "$DATABASE_URL" -f drizzle/0261_thread_turns_origin_turn_index.sql
|
|
8
|
+
-- creates: public.idx_thread_turns_origin_turn
|
|
9
|
+
|
|
10
|
+
CREATE INDEX IF NOT EXISTS idx_thread_turns_origin_turn
|
|
11
|
+
ON public.thread_turns (origin_turn_id);
|
|
@@ -105,6 +105,18 @@ variable "enable_hindsight" {
|
|
|
105
105
|
default = true
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
variable "enable_capability_broker" {
|
|
109
|
+
description = "THINK-280 governed capability runtime. When true, brings up the capability broker (PoP session table, private REST API, dedicated execute-api VPCE with private DNS off, no-NAT interpreter SG) and enables capability-private interpreter provisioning. Default false — currently flipped on only for the dev dogfood via deploy.yml."
|
|
110
|
+
type = bool
|
|
111
|
+
default = false
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
variable "analyst_lambda_vpc_egress" {
|
|
115
|
+
description = "VPC-attach the analyst data-path Lambdas so their egress rides the NAT gateway's stable EIP — required when an external analyst data source sits behind an IP allowlist. See the thinkwork module's analyst_egress_ip output for the IP to allowlist."
|
|
116
|
+
type = bool
|
|
117
|
+
default = false
|
|
118
|
+
}
|
|
119
|
+
|
|
108
120
|
variable "memory_engine" {
|
|
109
121
|
description = "Active long-term memory engine. Empty selects Hindsight when enable_hindsight = true. Use 'agentcore' only for explicit low-cost/development managed-memory deployments."
|
|
110
122
|
type = string
|
|
@@ -416,6 +428,18 @@ variable "brain_dream_state_enabled" {
|
|
|
416
428
|
default = false
|
|
417
429
|
}
|
|
418
430
|
|
|
431
|
+
variable "retry_dispatcher_enabled" {
|
|
432
|
+
description = "Enable the recurring retry-dispatcher schedule (THINK-307). Never enable on a stage before THINK-305, THINK-308, and THINK-309 are live there; manual invokes work regardless."
|
|
433
|
+
type = bool
|
|
434
|
+
default = false
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
variable "stall_threshold_minutes" {
|
|
438
|
+
description = "Minutes of turn inactivity before the stall monitor marks a running turn timed_out; the retry dispatcher reads the same knob as its origin-freshness guard (THINK-306/THINK-307)."
|
|
439
|
+
type = number
|
|
440
|
+
default = 5
|
|
441
|
+
}
|
|
442
|
+
|
|
419
443
|
variable "requester_memory_dreaming_model_id" {
|
|
420
444
|
description = "Bedrock Converse model id for requester memory REM reflection."
|
|
421
445
|
type = string
|
|
@@ -460,6 +484,12 @@ variable "analyst_policy_source" {
|
|
|
460
484
|
}
|
|
461
485
|
}
|
|
462
486
|
|
|
487
|
+
variable "capability_self_extension_tenants" {
|
|
488
|
+
description = "Governed autonomy opt-in: comma/space-separated tenant ids allowed to autonomously self-extend capabilities (self_admit_connection / self_approve_routine). Empty (default) = NO tenant enabled; self-extension ships inert and fail-closed."
|
|
489
|
+
type = string
|
|
490
|
+
default = ""
|
|
491
|
+
}
|
|
492
|
+
|
|
463
493
|
variable "wiki_aggregation_pass_enabled" {
|
|
464
494
|
description = <<-EOT
|
|
465
495
|
Feature flag for the wiki aggregation pass — the second LLM call
|
|
@@ -749,6 +779,8 @@ module "thinkwork" {
|
|
|
749
779
|
db_password = var.db_password
|
|
750
780
|
database_engine = var.database_engine
|
|
751
781
|
enable_hindsight = var.enable_hindsight
|
|
782
|
+
enable_capability_broker = var.enable_capability_broker
|
|
783
|
+
analyst_lambda_vpc_egress = var.analyst_lambda_vpc_egress
|
|
752
784
|
hindsight_database_name = var.hindsight_database_name
|
|
753
785
|
memory_engine = var.memory_engine
|
|
754
786
|
twenty_provisioned = var.twenty_provisioned
|
|
@@ -831,6 +863,7 @@ module "thinkwork" {
|
|
|
831
863
|
wiki_aggregation_pass_enabled = var.wiki_aggregation_pass_enabled
|
|
832
864
|
wiki_source = var.wiki_source
|
|
833
865
|
analyst_policy_source = var.analyst_policy_source
|
|
866
|
+
capability_self_extension_tenants = var.capability_self_extension_tenants
|
|
834
867
|
knowledge_graph_observations_ingest_enabled = var.knowledge_graph_observations_ingest_enabled
|
|
835
868
|
wiki_deterministic_linking_enabled = var.wiki_deterministic_linking_enabled
|
|
836
869
|
google_places_api_key = var.google_places_api_key
|
|
@@ -838,6 +871,8 @@ module "thinkwork" {
|
|
|
838
871
|
requester_memory_dreaming_enabled = var.requester_memory_dreaming_enabled
|
|
839
872
|
requester_memory_dreaming_schedule_expression = var.requester_memory_dreaming_schedule_expression
|
|
840
873
|
brain_dream_state_enabled = var.brain_dream_state_enabled
|
|
874
|
+
retry_dispatcher_enabled = var.retry_dispatcher_enabled
|
|
875
|
+
stall_threshold_minutes = var.stall_threshold_minutes
|
|
841
876
|
requester_memory_dreaming_model_id = var.requester_memory_dreaming_model_id
|
|
842
877
|
agentcore_code_interpreter_id = var.agentcore_code_interpreter_id
|
|
843
878
|
agentcore_memory_id = var.agentcore_memory_id
|
|
@@ -1316,3 +1351,8 @@ output "customer_domain_name_servers" {
|
|
|
1316
1351
|
description = "The four Route53 name servers for the customer-domain zone — publish these via the claim tool's `claim --set-targets` to delegate (empty when no customer domain is configured)."
|
|
1317
1352
|
value = module.thinkwork.customer_domain_name_servers
|
|
1318
1353
|
}
|
|
1354
|
+
|
|
1355
|
+
output "analyst_egress_ip" {
|
|
1356
|
+
description = "Stable NAT egress IP of the VPC-attached analyst data-path Lambdas (null unless analyst_lambda_vpc_egress is set)"
|
|
1357
|
+
value = module.thinkwork.analyst_egress_ip
|
|
1358
|
+
}
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
################################################################################
|
|
2
|
+
# AgentCore Admin — App Module (stage-level)
|
|
3
|
+
#
|
|
4
|
+
# The `agentcore-admin` Lambda is the control-plane worker that regular sandbox
|
|
5
|
+
# provisioning depends on: `createTenant` (packages/api) RequestResponse-invokes
|
|
6
|
+
# it to create per-tenant IAM roles + AgentCore Code Interpreters, and it also
|
|
7
|
+
# serves SSM permission-profile CRUD + CloudWatch audit queries. The handler
|
|
8
|
+
# lives at packages/lambda/agentcore-admin.ts and is bundled by
|
|
9
|
+
# scripts/build-lambdas.sh into dist/lambdas/agentcore-admin.zip.
|
|
10
|
+
#
|
|
11
|
+
# Historically this Lambda was built but instantiated NOWHERE — so sandbox
|
|
12
|
+
# provisioning would fail closed at runtime (AGENTCORE_ADMIN_LAMBDA_ARN unset).
|
|
13
|
+
# This module deploys it. It is intentionally a SEPARATE module with a DEDICATED
|
|
14
|
+
# least-privilege role: the handler holds a dangerous control-plane surface
|
|
15
|
+
# (iam:CreateRole, bedrock-agentcore CreateCodeInterpreter) that must never leak
|
|
16
|
+
# onto the ~90-handler shared lambda-api role.
|
|
17
|
+
#
|
|
18
|
+
# THINK-280 capability-private wiring: CAPABILITY_PRIVATE_SUBNET_IDS /
|
|
19
|
+
# CAPABILITY_PRIVATE_SECURITY_GROUP_IDS come from the capability-broker outputs
|
|
20
|
+
# (via the agentcore-code-interpreter module). Both are EMPTY when the broker is
|
|
21
|
+
# disabled (var.enable_capability_broker=false, the default) — the handler then
|
|
22
|
+
# skips capability-private provisioning entirely (capabilityPrivateProvisioning
|
|
23
|
+
# Enabled() returns false). The module therefore ships INERT for the
|
|
24
|
+
# capability-private path while still fixing the regular-provisioning gap.
|
|
25
|
+
################################################################################
|
|
26
|
+
|
|
27
|
+
terraform {
|
|
28
|
+
required_providers {
|
|
29
|
+
aws = {
|
|
30
|
+
source = "hashicorp/aws"
|
|
31
|
+
version = ">= 5.0"
|
|
32
|
+
}
|
|
33
|
+
random = {
|
|
34
|
+
source = "hashicorp/random"
|
|
35
|
+
version = ">= 3.0"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
variable "stage" {
|
|
41
|
+
description = "Deployment stage (dev, prod, etc.)."
|
|
42
|
+
type = string
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
variable "region" {
|
|
46
|
+
description = "AWS region."
|
|
47
|
+
type = string
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
variable "account_id" {
|
|
51
|
+
description = "AWS account ID (used to construct IAM resource ARNs)."
|
|
52
|
+
type = string
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# ---- Lambda artifact sources (mirror lambda-api/remote-artifacts.tf) ----------
|
|
56
|
+
variable "lambda_zips_dir" {
|
|
57
|
+
description = "Local directory of built Lambda zips (source-checkout deploys). Mutually exclusive with lambda_artifact_bucket."
|
|
58
|
+
type = string
|
|
59
|
+
default = ""
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
variable "lambda_artifact_bucket" {
|
|
63
|
+
description = "S3 bucket holding release Lambda zips (enterprise deploys). Mutually exclusive with lambda_zips_dir."
|
|
64
|
+
type = string
|
|
65
|
+
default = ""
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
variable "lambda_artifact_prefix" {
|
|
69
|
+
description = "S3 key prefix for release Lambda zips."
|
|
70
|
+
type = string
|
|
71
|
+
default = ""
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
# ---- Database (handler reads the tenants table via getDb() → DATABASE_URL) -----
|
|
75
|
+
variable "db_username" {
|
|
76
|
+
description = "Database username (used to construct DATABASE_URL)."
|
|
77
|
+
type = string
|
|
78
|
+
default = "thinkwork_admin"
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
variable "db_password" {
|
|
82
|
+
description = "Database password (used to construct DATABASE_URL)."
|
|
83
|
+
type = string
|
|
84
|
+
sensitive = true
|
|
85
|
+
default = ""
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
variable "db_cluster_endpoint" {
|
|
89
|
+
description = "Aurora cluster writer endpoint."
|
|
90
|
+
type = string
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
variable "database_name" {
|
|
94
|
+
description = "Database name."
|
|
95
|
+
type = string
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
variable "graphql_db_secret_arn" {
|
|
99
|
+
description = "ARN of the writer DB credentials secret (granted GetSecretValue so db.ts's secret fallback works)."
|
|
100
|
+
type = string
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
# ---- Capability-private interpreter VPC placement (THINK-280 U4) ---------------
|
|
104
|
+
# Empty when the capability broker is disabled → handler skips capability-private
|
|
105
|
+
# provisioning. Sourced from the broker outputs via the code-interpreter module.
|
|
106
|
+
variable "capability_private_subnet_ids" {
|
|
107
|
+
description = "No-NAT subnets for the capability-private VPC-mode interpreter. Empty when the broker is disabled."
|
|
108
|
+
type = list(string)
|
|
109
|
+
default = []
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
variable "capability_private_security_group_ids" {
|
|
113
|
+
description = "Egress-only SGs for the capability-private interpreter. Empty when the broker is disabled."
|
|
114
|
+
type = list(string)
|
|
115
|
+
default = []
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
################################################################################
|
|
119
|
+
# Artifact-source gating — identical semantics to lambda-api/remote-artifacts.tf.
|
|
120
|
+
# Exactly one source (local zips OR remote S3) must be set, else the Lambda is
|
|
121
|
+
# not deployed (scaffolded installs with no artifacts).
|
|
122
|
+
################################################################################
|
|
123
|
+
|
|
124
|
+
locals {
|
|
125
|
+
use_local_zips = trimspace(var.lambda_zips_dir) != ""
|
|
126
|
+
use_remote_lambda_artifacts = trimspace(var.lambda_artifact_bucket) != ""
|
|
127
|
+
lambda_artifact_prefix = trim(trimspace(var.lambda_artifact_prefix), "/")
|
|
128
|
+
artifact_source_count = (local.use_local_zips ? 1 : 0) + (local.use_remote_lambda_artifacts ? 1 : 0)
|
|
129
|
+
deploy = local.artifact_source_count == 1
|
|
130
|
+
|
|
131
|
+
function_name = "thinkwork-${var.stage}-api-agentcore-admin"
|
|
132
|
+
|
|
133
|
+
# DATABASE_URL built exactly as lambda-api's common_env does so getDb()
|
|
134
|
+
# resolves identically. sslmode=no-verify matches the shared pool (Aurora is
|
|
135
|
+
# reachable over TLS without a bundled CA in this stack).
|
|
136
|
+
database_url = "postgresql://${var.db_username}:${urlencode(var.db_password)}@${var.db_cluster_endpoint}:5432/${var.database_name}?sslmode=no-verify"
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
################################################################################
|
|
140
|
+
# Admin bearer token — random_password → Secrets Manager. The token value is
|
|
141
|
+
# ALSO injected as the Lambda's AGENTCORE_ADMIN_TOKEN env (the handler compares
|
|
142
|
+
# the Bearer header against process.env directly), and handed to graphql-http so
|
|
143
|
+
# its invoke carries the matching credential. The secret is the durable record
|
|
144
|
+
# of truth for operators / rotation.
|
|
145
|
+
################################################################################
|
|
146
|
+
|
|
147
|
+
resource "random_password" "admin_token" {
|
|
148
|
+
count = local.deploy ? 1 : 0
|
|
149
|
+
# 48 chars, alphanumeric only — safe inside an `Authorization: Bearer` header.
|
|
150
|
+
length = 48
|
|
151
|
+
special = false
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
resource "aws_secretsmanager_secret" "admin_token" {
|
|
155
|
+
count = local.deploy ? 1 : 0
|
|
156
|
+
name = "thinkwork/${var.stage}/agentcore-admin-token"
|
|
157
|
+
description = "Bearer token the agentcore-admin Lambda validates; graphql-http presents it on RequestResponse invokes."
|
|
158
|
+
|
|
159
|
+
tags = {
|
|
160
|
+
Name = "thinkwork-${var.stage}-agentcore-admin-token"
|
|
161
|
+
Stage = var.stage
|
|
162
|
+
Purpose = "agentcore-admin-bearer-token"
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
resource "aws_secretsmanager_secret_version" "admin_token" {
|
|
167
|
+
count = local.deploy ? 1 : 0
|
|
168
|
+
secret_id = aws_secretsmanager_secret.admin_token[0].id
|
|
169
|
+
secret_string = random_password.admin_token[0].result
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
################################################################################
|
|
173
|
+
# IAM role — DEDICATED, least-privilege. Scoped tightly to the handler's actual
|
|
174
|
+
# AWS SDK calls (see packages/lambda/agentcore-admin.ts).
|
|
175
|
+
################################################################################
|
|
176
|
+
|
|
177
|
+
resource "aws_iam_role" "admin" {
|
|
178
|
+
count = local.deploy ? 1 : 0
|
|
179
|
+
name = "thinkwork-${var.stage}-agentcore-admin"
|
|
180
|
+
|
|
181
|
+
assume_role_policy = jsonencode({
|
|
182
|
+
Version = "2012-10-17"
|
|
183
|
+
Statement = [{
|
|
184
|
+
Effect = "Allow"
|
|
185
|
+
Principal = { Service = "lambda.amazonaws.com" }
|
|
186
|
+
Action = "sts:AssumeRole"
|
|
187
|
+
}]
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
tags = {
|
|
191
|
+
Name = "thinkwork-${var.stage}-agentcore-admin"
|
|
192
|
+
Stage = var.stage
|
|
193
|
+
Purpose = "agentcore-admin-execution-role"
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
resource "aws_iam_role_policy" "admin" {
|
|
198
|
+
count = local.deploy ? 1 : 0
|
|
199
|
+
name = "agentcore-admin"
|
|
200
|
+
role = aws_iam_role.admin[0].id
|
|
201
|
+
|
|
202
|
+
policy = jsonencode({
|
|
203
|
+
Version = "2012-10-17"
|
|
204
|
+
Statement = [
|
|
205
|
+
# --- Lambda's own log group (basic execution) ---
|
|
206
|
+
{
|
|
207
|
+
Sid = "LambdaOwnLogs"
|
|
208
|
+
Effect = "Allow"
|
|
209
|
+
Action = [
|
|
210
|
+
"logs:CreateLogGroup",
|
|
211
|
+
"logs:CreateLogStream",
|
|
212
|
+
"logs:PutLogEvents",
|
|
213
|
+
]
|
|
214
|
+
Resource = "arn:aws:logs:${var.region}:${var.account_id}:log-group:/aws/lambda/${local.function_name}:*"
|
|
215
|
+
},
|
|
216
|
+
# --- CloudWatch audit query (queryAuditLogs) ---
|
|
217
|
+
{
|
|
218
|
+
Sid = "AuditLogQuery"
|
|
219
|
+
Effect = "Allow"
|
|
220
|
+
Action = ["logs:FilterLogEvents"]
|
|
221
|
+
Resource = "arn:aws:logs:${var.region}:${var.account_id}:log-group:/thinkwork/${var.stage}/agentcore/agents:*"
|
|
222
|
+
},
|
|
223
|
+
# --- SSM permission-profile CRUD (getPermissions / putPermissions) ---
|
|
224
|
+
{
|
|
225
|
+
Sid = "SsmPermissionProfiles"
|
|
226
|
+
Effect = "Allow"
|
|
227
|
+
Action = [
|
|
228
|
+
"ssm:GetParameter",
|
|
229
|
+
"ssm:PutParameter",
|
|
230
|
+
]
|
|
231
|
+
Resource = "arn:aws:ssm:${var.region}:${var.account_id}:parameter/thinkwork/${var.stage}/*"
|
|
232
|
+
},
|
|
233
|
+
# --- Per-tenant sandbox IAM roles (provision / deprovision) ---
|
|
234
|
+
# Two name prefixes: computeRoleName → sandbox-tenant-*, and the U4
|
|
235
|
+
# capability-private role → cappriv-tenant-*.
|
|
236
|
+
{
|
|
237
|
+
Sid = "SandboxRoleLifecycle"
|
|
238
|
+
Effect = "Allow"
|
|
239
|
+
Action = [
|
|
240
|
+
"iam:CreateRole",
|
|
241
|
+
"iam:DeleteRole",
|
|
242
|
+
"iam:GetRole",
|
|
243
|
+
"iam:PutRolePolicy",
|
|
244
|
+
"iam:DeleteRolePolicy",
|
|
245
|
+
"iam:TagRole",
|
|
246
|
+
]
|
|
247
|
+
Resource = [
|
|
248
|
+
"arn:aws:iam::${var.account_id}:role/thinkwork-${var.stage}-sandbox-*",
|
|
249
|
+
"arn:aws:iam::${var.account_id}:role/thinkwork-${var.stage}-cappriv-*",
|
|
250
|
+
]
|
|
251
|
+
},
|
|
252
|
+
# --- PassRole the sandbox roles to AgentCore (CreateCodeInterpreter) ---
|
|
253
|
+
{
|
|
254
|
+
Sid = "PassSandboxRolesToAgentCore"
|
|
255
|
+
Effect = "Allow"
|
|
256
|
+
Action = ["iam:PassRole"]
|
|
257
|
+
Resource = [
|
|
258
|
+
"arn:aws:iam::${var.account_id}:role/thinkwork-${var.stage}-sandbox-*",
|
|
259
|
+
"arn:aws:iam::${var.account_id}:role/thinkwork-${var.stage}-cappriv-*",
|
|
260
|
+
]
|
|
261
|
+
Condition = {
|
|
262
|
+
StringEquals = {
|
|
263
|
+
"iam:PassedToService" = "bedrock-agentcore.amazonaws.com"
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
# --- AgentCore Code Interpreter control plane ---
|
|
268
|
+
# List/Create/Delete are account-scoped operations without a stable
|
|
269
|
+
# per-resource ARN at plan time; the SDK client is the control plane.
|
|
270
|
+
{
|
|
271
|
+
Sid = "CodeInterpreterControlPlane"
|
|
272
|
+
Effect = "Allow"
|
|
273
|
+
Action = [
|
|
274
|
+
"bedrock-agentcore:CreateCodeInterpreter",
|
|
275
|
+
"bedrock-agentcore:DeleteCodeInterpreter",
|
|
276
|
+
"bedrock-agentcore:ListCodeInterpreters",
|
|
277
|
+
"bedrock-agentcore:GetCodeInterpreter",
|
|
278
|
+
# CreateCodeInterpreter passes inline `tags`, which AgentCore
|
|
279
|
+
# authorizes as a separate bedrock-agentcore:TagResource action on
|
|
280
|
+
# code-interpreter-custom/* — verified empirically (first provision
|
|
281
|
+
# returned AccessDenied on TagResource). UntagResource covers teardown.
|
|
282
|
+
"bedrock-agentcore:TagResource",
|
|
283
|
+
"bedrock-agentcore:UntagResource",
|
|
284
|
+
]
|
|
285
|
+
Resource = "*"
|
|
286
|
+
},
|
|
287
|
+
# --- Secrets: DB credentials (db.ts secret fallback) + own admin token ---
|
|
288
|
+
{
|
|
289
|
+
Sid = "SecretsRead"
|
|
290
|
+
Effect = "Allow"
|
|
291
|
+
Action = ["secretsmanager:GetSecretValue"]
|
|
292
|
+
Resource = [
|
|
293
|
+
var.graphql_db_secret_arn,
|
|
294
|
+
aws_secretsmanager_secret.admin_token[0].arn,
|
|
295
|
+
]
|
|
296
|
+
},
|
|
297
|
+
]
|
|
298
|
+
})
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
################################################################################
|
|
302
|
+
# Lambda function
|
|
303
|
+
################################################################################
|
|
304
|
+
|
|
305
|
+
resource "aws_lambda_function" "admin" {
|
|
306
|
+
count = local.deploy ? 1 : 0
|
|
307
|
+
|
|
308
|
+
function_name = local.function_name
|
|
309
|
+
role = aws_iam_role.admin[0].arn
|
|
310
|
+
handler = "index.handler"
|
|
311
|
+
runtime = "nodejs20.x"
|
|
312
|
+
timeout = 60
|
|
313
|
+
memory_size = 256
|
|
314
|
+
|
|
315
|
+
filename = local.use_local_zips ? "${var.lambda_zips_dir}/agentcore-admin.zip" : null
|
|
316
|
+
source_code_hash = local.use_local_zips ? filebase64sha256("${var.lambda_zips_dir}/agentcore-admin.zip") : null
|
|
317
|
+
s3_bucket = local.use_remote_lambda_artifacts ? var.lambda_artifact_bucket : null
|
|
318
|
+
s3_key = local.use_remote_lambda_artifacts ? "${local.lambda_artifact_prefix}/agentcore-admin.zip" : null
|
|
319
|
+
|
|
320
|
+
environment {
|
|
321
|
+
variables = {
|
|
322
|
+
STAGE = var.stage
|
|
323
|
+
AWS_ACCOUNT_ID = var.account_id
|
|
324
|
+
NODE_OPTIONS = "--enable-source-maps"
|
|
325
|
+
DATABASE_URL = local.database_url
|
|
326
|
+
# Bearer token the handler validates. Secret env is the sanctioned home
|
|
327
|
+
# for secrets in this stack (never the SSM runtime-config String doc).
|
|
328
|
+
AGENTCORE_ADMIN_TOKEN = random_password.admin_token[0].result
|
|
329
|
+
# THINK-280 U4 — capability-private interpreter VPC placement. EMPTY when
|
|
330
|
+
# the broker is disabled → the handler skips capability-private entirely.
|
|
331
|
+
CAPABILITY_PRIVATE_SUBNET_IDS = join(",", var.capability_private_subnet_ids)
|
|
332
|
+
CAPABILITY_PRIVATE_SECURITY_GROUP_IDS = join(",", var.capability_private_security_group_ids)
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
tags = {
|
|
337
|
+
Name = local.function_name
|
|
338
|
+
Stage = var.stage
|
|
339
|
+
Purpose = "agentcore-admin"
|
|
340
|
+
}
|
|
341
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
output "lambda_arn" {
|
|
2
|
+
description = "ARN of the agentcore-admin Lambda (AGENTCORE_ADMIN_LAMBDA_ARN for graphql-http). Empty when not deployed."
|
|
3
|
+
value = local.deploy ? aws_lambda_function.admin[0].arn : ""
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
output "lambda_function_name" {
|
|
7
|
+
description = "Function name of the agentcore-admin Lambda. Empty when not deployed."
|
|
8
|
+
value = local.deploy ? aws_lambda_function.admin[0].function_name : ""
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
output "admin_token" {
|
|
12
|
+
description = "Bearer token graphql-http presents on invoke (AGENTCORE_ADMIN_TOKEN). Empty when not deployed."
|
|
13
|
+
value = local.deploy ? random_password.admin_token[0].result : ""
|
|
14
|
+
sensitive = true
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
output "admin_token_secret_arn" {
|
|
18
|
+
description = "ARN of the Secrets Manager secret holding the admin bearer token. Empty when not deployed."
|
|
19
|
+
value = local.deploy ? aws_secretsmanager_secret.admin_token[0].arn : ""
|
|
20
|
+
}
|
|
@@ -50,6 +50,23 @@ variable "image_retention_count" {
|
|
|
50
50
|
default = 10
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
# THINK-280 U4 — VPC placement for the capability-private interpreter. Sourced
|
|
54
|
+
# (when the capability broker is enabled) from the broker module's no-NAT
|
|
55
|
+
# interpreter subnets + dedicated egress-only SG. Empty by default: the
|
|
56
|
+
# agentcore-admin Lambda skips capability-private provisioning entirely when
|
|
57
|
+
# either list is empty, so the module stays inert with the broker disabled.
|
|
58
|
+
variable "capability_private_subnet_ids" {
|
|
59
|
+
description = "No-NAT private subnets the capability-private VPC-mode interpreter attaches to. Empty when the broker is disabled."
|
|
60
|
+
type = list(string)
|
|
61
|
+
default = []
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
variable "capability_private_security_group_ids" {
|
|
65
|
+
description = "Egress-only security groups (reach only the broker execute-api VPCE) for the capability-private interpreter. Empty when the broker is disabled."
|
|
66
|
+
type = list(string)
|
|
67
|
+
default = []
|
|
68
|
+
}
|
|
69
|
+
|
|
53
70
|
# Environment catalog. Kept as a locals block (not a variable) because v1
|
|
54
71
|
# semantics are fixed in the plan — extending requires a reviewable PR,
|
|
55
72
|
# not a tfvars tweak.
|
|
@@ -63,6 +80,14 @@ locals {
|
|
|
63
80
|
description = "S3 + DNS + AWS service endpoints only; no public egress."
|
|
64
81
|
network_mode = "SANDBOX"
|
|
65
82
|
}
|
|
83
|
+
# THINK-280 U4: VPC-mode interpreter, no NAT. Reaches ONLY the capability
|
|
84
|
+
# broker's private execute-api VPCE (+ DNS). Runtime-selected per invocation
|
|
85
|
+
# (not a template-level opt-in), and only provisioned when the broker is
|
|
86
|
+
# enabled — the agentcore-admin Lambda gates on the VPC subnet/SG env vars.
|
|
87
|
+
"capability-private" = {
|
|
88
|
+
description = "VPC-mode, no NAT; reaches only the capability broker execute-api VPCE."
|
|
89
|
+
network_mode = "VPC"
|
|
90
|
+
}
|
|
66
91
|
}
|
|
67
92
|
}
|
|
68
93
|
|
|
@@ -155,6 +180,27 @@ locals {
|
|
|
155
180
|
},
|
|
156
181
|
]
|
|
157
182
|
})
|
|
183
|
+
|
|
184
|
+
# THINK-280 U4: SEPARATE restricted inline policy for the capability-private
|
|
185
|
+
# VPC-mode interpreter role. It deliberately LACKS the tenant sandbox Secrets
|
|
186
|
+
# Manager wildcard (SandboxSecretsRead above), tenant S3, and any database /
|
|
187
|
+
# provider access — the private interpreter reaches providers only through the
|
|
188
|
+
# broker's PoP session (AE5). Logs are the only genuine boot requirement.
|
|
189
|
+
# Consumed by the agentcore-admin Lambda for the capability-private role.
|
|
190
|
+
capability_private_role_inline_policy_template = jsonencode({
|
|
191
|
+
Version = "2012-10-17"
|
|
192
|
+
Statement = [
|
|
193
|
+
{
|
|
194
|
+
Sid = "CapabilityPrivateCloudWatchLogs"
|
|
195
|
+
Effect = "Allow"
|
|
196
|
+
Action = [
|
|
197
|
+
"logs:CreateLogStream",
|
|
198
|
+
"logs:PutLogEvents",
|
|
199
|
+
]
|
|
200
|
+
Resource = "arn:aws:logs:${var.region}:${var.account_id}:log-group:/aws/bedrock-agentcore/runtimes/*"
|
|
201
|
+
},
|
|
202
|
+
]
|
|
203
|
+
})
|
|
158
204
|
}
|
|
159
205
|
|
|
160
206
|
################################################################################
|
|
@@ -191,6 +237,21 @@ output "tenant_role_inline_policy_template" {
|
|
|
191
237
|
value = local.tenant_role_inline_policy_template
|
|
192
238
|
}
|
|
193
239
|
|
|
240
|
+
output "capability_private_role_inline_policy_template" {
|
|
241
|
+
description = "THINK-280 U4: JSON inline-policy for the capability-private interpreter role. Logs-only; NO Secrets Manager / S3 / DB access."
|
|
242
|
+
value = local.capability_private_role_inline_policy_template
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
output "capability_private_subnet_ids" {
|
|
246
|
+
description = "No-NAT subnets the capability-private interpreter attaches to (CAPABILITY_PRIVATE_SUBNET_IDS). Empty when the broker is disabled."
|
|
247
|
+
value = var.capability_private_subnet_ids
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
output "capability_private_security_group_ids" {
|
|
251
|
+
description = "Egress-only SGs for the capability-private interpreter (CAPABILITY_PRIVATE_SECURITY_GROUP_IDS). Empty when the broker is disabled."
|
|
252
|
+
value = var.capability_private_security_group_ids
|
|
253
|
+
}
|
|
254
|
+
|
|
194
255
|
output "stage" {
|
|
195
256
|
description = "Echo of the stage variable (convenience for downstream modules)."
|
|
196
257
|
value = var.stage
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
################################################################################
|
|
2
|
+
# AgentCore Harness — App Module (THINK-311 U4, ship-inert)
|
|
3
|
+
#
|
|
4
|
+
# IAM surface for the AWS Bedrock AgentCore Harness trial: the execution role
|
|
5
|
+
# Harness microVMs assume, plus (via outputs consumed by lambda-api's grouped
|
|
6
|
+
# policies) the invoker grants the shared api Lambda role needs to create and
|
|
7
|
+
# invoke Harness resources. Nothing reads or invokes any of this until the
|
|
8
|
+
# harness-runner handler lands in U5 — this module is pure IAM.
|
|
9
|
+
#
|
|
10
|
+
# Trust-policy note: sibling agentcore roles (agentcore-pi, agentcore-runtime)
|
|
11
|
+
# trust the bare `bedrock-agentcore.amazonaws.com` service principal with no
|
|
12
|
+
# account/source-arn conditions; this role mirrors that repo pattern.
|
|
13
|
+
################################################################################
|
|
14
|
+
|
|
15
|
+
################################################################################
|
|
16
|
+
# Harness execution role — assumed by Harness microVMs
|
|
17
|
+
################################################################################
|
|
18
|
+
|
|
19
|
+
resource "aws_iam_role" "harness_execution" {
|
|
20
|
+
count = var.enabled ? 1 : 0
|
|
21
|
+
|
|
22
|
+
name = "thinkwork-${var.stage}-agentcore-harness-role"
|
|
23
|
+
|
|
24
|
+
assume_role_policy = jsonencode({
|
|
25
|
+
Version = "2012-10-17"
|
|
26
|
+
Statement = [{
|
|
27
|
+
Effect = "Allow"
|
|
28
|
+
Principal = { Service = "bedrock-agentcore.amazonaws.com" }
|
|
29
|
+
Action = "sts:AssumeRole"
|
|
30
|
+
}]
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
tags = {
|
|
34
|
+
Name = "thinkwork-${var.stage}-agentcore-harness-role"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
resource "aws_iam_role_policy" "harness_execution" {
|
|
39
|
+
count = var.enabled ? 1 : 0
|
|
40
|
+
|
|
41
|
+
name = "agentcore-harness-permissions"
|
|
42
|
+
role = aws_iam_role.harness_execution[0].id
|
|
43
|
+
|
|
44
|
+
policy = jsonencode({
|
|
45
|
+
Version = "2012-10-17"
|
|
46
|
+
Statement = [
|
|
47
|
+
{
|
|
48
|
+
# Cross-region inference profiles (us.anthropic.claude-*) require
|
|
49
|
+
# `bedrock:InvokeModel` on the *inference-profile* ARN AND on the
|
|
50
|
+
# underlying foundation-model ARN in every region the profile can
|
|
51
|
+
# route to — same resource list the api Lambda's grouped ai policy
|
|
52
|
+
# uses (see ../lambda-api/iam-grouped.tf "bedrock-invoke").
|
|
53
|
+
Sid = "BedrockInvoke"
|
|
54
|
+
Effect = "Allow"
|
|
55
|
+
Action = ["bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream"]
|
|
56
|
+
Resource = [
|
|
57
|
+
"arn:aws:bedrock:*::foundation-model/*",
|
|
58
|
+
"arn:aws:bedrock:*:${var.account_id}:inference-profile/*",
|
|
59
|
+
]
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
# Tenant skill catalog sources + materialized workspace skill folders
|
|
63
|
+
# live under tenants/* in the stage workspace bucket. Read-only —
|
|
64
|
+
# Harness microVMs never write back to the workspace bucket.
|
|
65
|
+
Sid = "WorkspaceSkillSourcesRead"
|
|
66
|
+
Effect = "Allow"
|
|
67
|
+
Action = ["s3:GetObject"]
|
|
68
|
+
Resource = "arn:aws:s3:::${var.bucket_name}/tenants/*"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
Sid = "WorkspaceSkillSourcesList"
|
|
72
|
+
Effect = "Allow"
|
|
73
|
+
Action = ["s3:ListBucket"]
|
|
74
|
+
Resource = "arn:aws:s3:::${var.bucket_name}"
|
|
75
|
+
Condition = {
|
|
76
|
+
StringLike = {
|
|
77
|
+
"s3:prefix" = "tenants/*"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
# Harness containers log to the service-managed bedrock-agentcore
|
|
83
|
+
# log-group namespace, mirroring the agentcore-pi role's runtimes
|
|
84
|
+
# grant.
|
|
85
|
+
Sid = "CloudWatchLogs"
|
|
86
|
+
Effect = "Allow"
|
|
87
|
+
Action = [
|
|
88
|
+
"logs:CreateLogGroup",
|
|
89
|
+
"logs:CreateLogStream",
|
|
90
|
+
"logs:DescribeLogGroups",
|
|
91
|
+
"logs:DescribeLogStreams",
|
|
92
|
+
"logs:PutLogEvents",
|
|
93
|
+
]
|
|
94
|
+
Resource = [
|
|
95
|
+
"arn:aws:logs:${var.region}:${var.account_id}:log-group:/aws/bedrock-agentcore/*",
|
|
96
|
+
"arn:aws:logs:${var.region}:${var.account_id}:log-group:/aws/bedrock-agentcore/*:*",
|
|
97
|
+
]
|
|
98
|
+
},
|
|
99
|
+
]
|
|
100
|
+
})
|
|
101
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
output "execution_role_arn" {
|
|
2
|
+
description = "ARN of the Harness execution role (CreateHarness executionRoleArn). Empty string when the module is disabled."
|
|
3
|
+
value = var.enabled ? aws_iam_role.harness_execution[0].arn : ""
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
output "execution_role_name" {
|
|
7
|
+
description = "Name of the Harness execution role. Empty string when the module is disabled."
|
|
8
|
+
value = var.enabled ? aws_iam_role.harness_execution[0].name : ""
|
|
9
|
+
}
|