multi-tenant Phase 1: user registry + per-user memory

- CDK: add agent-claw-users DynamoDB table (actor_id PK, RETAIN policy)
- CDK: grant agent-runner read/write on users table; add USERS_TABLE_NAME env
- CDK: fix cdk.json app field (was object, must be command string)
- CDK: add UsersTableName output
- agent-runner: get_or_create_user() auto-registers users on first contact
  (stores display_name, telegram_username, created_at, allowed)
- agent-runner: pass user_profile in AgentCore payload
- prompt_builder: split base prompt (cached) from per-user context (injected per-call)
  removes USER.md/MEMORY.md from shared load; user name/username injected dynamically
- main.py: extract user_profile from payload, build user_context string for prompt
This commit is contained in:
daniel
2026-05-06 20:36:22 -05:00
parent 732b00fb66
commit 893c110729
16 changed files with 726 additions and 501 deletions

View File

@@ -1,238 +1,6 @@
{
"Description": "agent-claw: serverless personal assistant on AgentCore",
"Resources": {
"WorkspaceBucket53E30B92": {
"Type": "AWS::S3::Bucket",
"Properties": {
"BucketEncryption": {
"ServerSideEncryptionConfiguration": [
{
"ServerSideEncryptionByDefault": {
"SSEAlgorithm": "AES256"
}
}
]
},
"BucketName": "agent-claw-workspace-495395224548",
"Tags": [
{
"Key": "aws-cdk:cr-owned:254e75d0",
"Value": "true"
}
]
},
"UpdateReplacePolicy": "Retain",
"DeletionPolicy": "Retain",
"Metadata": {
"aws:cdk:path": "AgentClawStack/WorkspaceBucket/Resource"
}
},
"WorkspaceFilesAwsCliLayer50B6E9D8": {
"Type": "AWS::Lambda::LayerVersion",
"Properties": {
"Content": {
"S3Bucket": "cdk-hnb659fds-assets-495395224548-us-east-1",
"S3Key": "e2659170a0721541efa761a8d5d04d5e36cbbf691c4b15a9053002b7c825055d.zip"
},
"Description": "/opt/awscli/aws"
},
"Metadata": {
"aws:cdk:path": "AgentClawStack/WorkspaceFiles/AwsCliLayer/Resource",
"aws:asset:path": "asset.e2659170a0721541efa761a8d5d04d5e36cbbf691c4b15a9053002b7c825055d.zip",
"aws:asset:is-bundled": false,
"aws:asset:property": "Content"
}
},
"WorkspaceFilesCustomResourceA7FC771F": {
"Type": "Custom::CDKBucketDeployment",
"Properties": {
"ServiceToken": {
"Fn::GetAtt": [
"CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C81C01536",
"Arn"
]
},
"SourceBucketNames": [
"cdk-hnb659fds-assets-495395224548-us-east-1"
],
"SourceObjectKeys": [
"d5a4044422f3c0ab39b0d5bfa4e4ea2b1212f0d420a58b542fbc88917d7a676a.zip"
],
"DestinationBucketName": {
"Ref": "WorkspaceBucket53E30B92"
},
"WaitForDistributionInvalidation": true,
"Prune": true,
"OutputObjectKeys": true
},
"UpdateReplacePolicy": "Delete",
"DeletionPolicy": "Delete",
"Metadata": {
"aws:cdk:path": "AgentClawStack/WorkspaceFiles/CustomResource/Default"
}
},
"CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Statement": [
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
}
}
],
"Version": "2012-10-17"
},
"ManagedPolicyArns": [
{
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
]
]
}
]
},
"Metadata": {
"aws:cdk:path": "AgentClawStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole/Resource"
}
},
"CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRoleDefaultPolicy88902FDF": {
"Type": "AWS::IAM::Policy",
"Properties": {
"PolicyDocument": {
"Statement": [
{
"Action": [
"s3:GetObject*",
"s3:GetBucket*",
"s3:List*"
],
"Effect": "Allow",
"Resource": [
{
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":s3:::cdk-hnb659fds-assets-495395224548-us-east-1"
]
]
},
{
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":s3:::cdk-hnb659fds-assets-495395224548-us-east-1/*"
]
]
}
]
},
{
"Action": [
"s3:GetObject*",
"s3:GetBucket*",
"s3:List*",
"s3:DeleteObject*",
"s3:PutObject",
"s3:PutObjectLegalHold",
"s3:PutObjectRetention",
"s3:PutObjectTagging",
"s3:PutObjectVersionTagging",
"s3:Abort*"
],
"Effect": "Allow",
"Resource": [
{
"Fn::GetAtt": [
"WorkspaceBucket53E30B92",
"Arn"
]
},
{
"Fn::Join": [
"",
[
{
"Fn::GetAtt": [
"WorkspaceBucket53E30B92",
"Arn"
]
},
"/*"
]
]
}
]
}
],
"Version": "2012-10-17"
},
"PolicyName": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRoleDefaultPolicy88902FDF",
"Roles": [
{
"Ref": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265"
}
]
},
"Metadata": {
"aws:cdk:path": "AgentClawStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole/DefaultPolicy/Resource"
}
},
"CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C81C01536": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Code": {
"S3Bucket": "cdk-hnb659fds-assets-495395224548-us-east-1",
"S3Key": "3423a042b818e31c1e34a19d6689ab2e5f9b70fcbe9e71df66f241b20a200bd9.zip"
},
"Environment": {
"Variables": {
"AWS_CA_BUNDLE": "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem"
}
},
"Handler": "index.handler",
"Layers": [
{
"Ref": "WorkspaceFilesAwsCliLayer50B6E9D8"
}
],
"Role": {
"Fn::GetAtt": [
"CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265",
"Arn"
]
},
"Runtime": "python3.13",
"Timeout": 900
},
"DependsOn": [
"CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRoleDefaultPolicy88902FDF",
"CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265"
],
"Metadata": {
"aws:cdk:path": "AgentClawStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Resource",
"aws:asset:path": "asset.3423a042b818e31c1e34a19d6689ab2e5f9b70fcbe9e71df66f241b20a200bd9",
"aws:asset:is-bundled": false,
"aws:asset:property": "Code"
}
},
"SessionStore8C86EEFE": {
"Type": "AWS::DynamoDB::Table",
"Properties": {
@@ -261,6 +29,30 @@
"aws:cdk:path": "AgentClawStack/SessionStore/Resource"
}
},
"UsersTable9725E9C8": {
"Type": "AWS::DynamoDB::Table",
"Properties": {
"AttributeDefinitions": [
{
"AttributeName": "actor_id",
"AttributeType": "S"
}
],
"BillingMode": "PAY_PER_REQUEST",
"KeySchema": [
{
"AttributeName": "actor_id",
"KeyType": "HASH"
}
],
"TableName": "agent-claw-users"
},
"UpdateReplacePolicy": "Retain",
"DeletionPolicy": "Retain",
"Metadata": {
"aws:cdk:path": "AgentClawStack/UsersTable/Resource"
}
},
"MessageQueue7A3BF959": {
"Type": "AWS::SQS::Queue",
"Properties": {
@@ -356,7 +148,7 @@
"Properties": {
"Code": {
"S3Bucket": "cdk-hnb659fds-assets-495395224548-us-east-1",
"S3Key": "9d7af346bbad17b4c228d09e33a602eedc03747fe1cec1c7c9b7c8723ce74e5d.zip"
"S3Key": "8da48fd743d1e2cb70d8d1935cee795b6f8cf02609db05e2b8f28449be9ef875.zip"
},
"Environment": {
"Variables": {
@@ -385,7 +177,7 @@
],
"Metadata": {
"aws:cdk:path": "AgentClawStack/TgIngest/Resource",
"aws:asset:path": "asset.9d7af346bbad17b4c228d09e33a602eedc03747fe1cec1c7c9b7c8723ce74e5d",
"aws:asset:path": "asset.8da48fd743d1e2cb70d8d1935cee795b6f8cf02609db05e2b8f28449be9ef875",
"aws:asset:is-bundled": false,
"aws:asset:property": "Code"
}
@@ -467,6 +259,44 @@
}
]
},
{
"Action": [
"dynamodb:BatchGetItem",
"dynamodb:Query",
"dynamodb:GetItem",
"dynamodb:Scan",
"dynamodb:ConditionCheckItem",
"dynamodb:BatchWriteItem",
"dynamodb:PutItem",
"dynamodb:UpdateItem",
"dynamodb:DeleteItem",
"dynamodb:DescribeTable"
],
"Effect": "Allow",
"Resource": [
{
"Fn::GetAtt": [
"UsersTable9725E9C8",
"Arn"
]
}
]
},
{
"Action": [
"dynamodb:GetRecords",
"dynamodb:GetShardIterator"
],
"Effect": "Allow",
"Resource": [
{
"Fn::GetAtt": [
"UsersTable9725E9C8",
"Arn"
]
}
]
},
{
"Action": [
"s3:GetObject*",
@@ -476,22 +306,26 @@
"Effect": "Allow",
"Resource": [
{
"Fn::GetAtt": [
"WorkspaceBucket53E30B92",
"Arn"
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":s3:::agent-claw-workspace-495395224548"
]
]
},
{
"Fn::Join": [
"",
[
"arn:",
{
"Fn::GetAtt": [
"WorkspaceBucket53E30B92",
"Arn"
]
"Ref": "AWS::Partition"
},
"/*"
":s3:::agent-claw-workspace-495395224548/*"
]
]
}
@@ -553,20 +387,22 @@
"Properties": {
"Code": {
"S3Bucket": "cdk-hnb659fds-assets-495395224548-us-east-1",
"S3Key": "eeef9ac2146cd644e1727e77104b58bed992e19379d5070de3a05714ff2dba48.zip"
"S3Key": "7053cd1618f5f520a7aac409588128f920d8fe76791c1dbcc65610454d1a5387.zip"
},
"Environment": {
"Variables": {
"SESSION_TABLE_NAME": {
"Ref": "SessionStore8C86EEFE"
},
"WORKSPACE_BUCKET_NAME": {
"Ref": "WorkspaceBucket53E30B92"
},
"WORKSPACE_BUCKET_NAME": "agent-claw-workspace-495395224548",
"TELEGRAM_BOT_TOKEN_SECRET_ARN": "arn:aws:secretsmanager:us-east-1:495395224548:secret:agent-claw/telegram-bot-token-Oq3in3",
"BRAVE_API_KEY_SECRET_ARN": "arn:aws:secretsmanager:us-east-1:495395224548:secret:agent-claw/brave-api-key-uUSgzi",
"RUNTIME_1_ARN": "PLACEHOLDER_SET_AFTER_RUNTIME_DEPLOY",
"AWS_REGION_NAME": "us-east-1"
"RUNTIME_1_ARN": "arn:aws:bedrock-agentcore:us-east-1:495395224548:runtime/agentclaw_agent_claw_main-vTRGIEG6ON",
"AWS_REGION_NAME": "us-east-1",
"USERS_TABLE_NAME": {
"Ref": "UsersTable9725E9C8"
},
"WORKSPACE_MCP_URL": "https://25hugrzw4uwtueeg77jsmft6lq0wunmd.lambda-url.us-east-1.on.aws/mcp"
}
},
"FunctionName": "agent-claw-agent-runner",
@@ -587,7 +423,7 @@
],
"Metadata": {
"aws:cdk:path": "AgentClawStack/AgentRunner/Resource",
"aws:asset:path": "asset.eeef9ac2146cd644e1727e77104b58bed992e19379d5070de3a05714ff2dba48",
"aws:asset:path": "asset.7053cd1618f5f520a7aac409588128f920d8fe76791c1dbcc65610454d1a5387",
"aws:asset:is-bundled": false,
"aws:asset:property": "Code"
}
@@ -752,22 +588,26 @@
"Effect": "Allow",
"Resource": [
{
"Fn::GetAtt": [
"WorkspaceBucket53E30B92",
"Arn"
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":s3:::agent-claw-workspace-495395224548"
]
]
},
{
"Fn::Join": [
"",
[
"arn:",
{
"Fn::GetAtt": [
"WorkspaceBucket53E30B92",
"Arn"
]
"Ref": "AWS::Partition"
},
"/*"
":s3:::agent-claw-workspace-495395224548/*"
]
]
}
@@ -797,6 +637,66 @@
],
"Effect": "Allow",
"Resource": "*"
},
{
"Action": "lambda:InvokeFunctionUrl",
"Condition": {
"StringEquals": {
"lambda:FunctionUrlAuthType": "AWS_IAM"
}
},
"Effect": "Allow",
"Resource": {
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":lambda:us-east-1:495395224548:function:agent-claw-workspace-mcp"
]
]
},
"Sid": "WorkspaceMcpInvoke"
},
{
"Action": [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret"
],
"Effect": "Allow",
"Resource": {
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":secretsmanager:us-east-1:495395224548:secret:agent-claw/google-workspace-credentials-??????"
]
]
}
},
{
"Action": [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret"
],
"Effect": "Allow",
"Resource": {
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":secretsmanager:us-east-1:495395224548:secret:agent-claw/google-oauth-client-??????"
]
]
}
}
],
"Version": "2012-10-17"
@@ -812,10 +712,65 @@
"aws:cdk:path": "AgentClawStack/Runtime1Role/DefaultPolicy/Resource"
}
},
"WorkspaceMcpRolePolicy5B8B0072": {
"Type": "AWS::IAM::Policy",
"Properties": {
"PolicyDocument": {
"Statement": [
{
"Action": [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret"
],
"Effect": "Allow",
"Resource": {
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":secretsmanager:us-east-1:495395224548:secret:agent-claw/google-workspace-credentials-??????"
]
]
}
},
{
"Action": [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret"
],
"Effect": "Allow",
"Resource": {
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":secretsmanager:us-east-1:495395224548:secret:agent-claw/google-oauth-client-??????"
]
]
}
}
],
"Version": "2012-10-17"
},
"PolicyName": "WorkspaceMcpRolePolicy5B8B0072",
"Roles": [
"agent-claw-workspace-mcp-role"
]
},
"Metadata": {
"aws:cdk:path": "AgentClawStack/WorkspaceMcpRole/Policy/Resource"
}
},
"CDKMetadata": {
"Type": "AWS::CDK::Metadata",
"Properties": {
"Analytics": "v2:deflate64:H4sIAAAAAAAA/22R0U7DMAxFv4X3LIxufMBWQCCBGC3itXJbr8qWJqV2VlVR/x0lZQMhnu7JvY4VO4lMbhO5vIKBFlV9XGhVSp8zVEeRIVnXVyhgoMLTSvqtq47IIt2bb5plC4SToFXha+y0HVs0LOfo7mIIIEImuQkyCQ1tWYP06d48w4j9B/akrBG5Mo1GtubBmYqDc4F0/2Pen9BwHp/3Al2nTBPi/90d9q2i0H0SClrpM6sxBFF3VqtqjHWRJlGPBlpbl9K/QzlXRpgEfZL0bw5dNCNMAjrVAOMA4ymR/pG523Qq5EHCMWdo4oUZgpVZxzM9Gcamh/OAf46xbjpvq9BhUwUMVGklNwOlWsXlibjU0D6O7Ihte/m90OYXvzruHE/C2Brlga5PyVrerOXy6kBKLXpnWLUos1m/AKsec0UeAgAA"
"Analytics": "v2:deflate64:H4sIAAAAAAAA/22PwU7DMAyGn2X31IxuPMCGQHBAjI775KZela1NSu1sqqK8O0rKOCBO/+ff+WO7hPKhhOUCr1zo5lx0poawF9RnVRE7P2pSeOVD4BVsvT6TbJFJNZPF3jU1hE+sO1KPR5shKv5iCB+efDYzRNVhXzcI4dlbLcbZ1PrlpwtZ2edRbzgMxrap/b+7o7E3zCl2y6d9ojLYQ6jcvErWneuMnnIoU1S8OiAzCcMmicLBtCh0xelSQngRGTaDSYEkqdwLtvnDGZJVOS8zvVqhdsTbOX/K/C5GlSel+M8B714GL1FZ1xCc+O5SruF+DcvFiY0pRm/F9ATVrN8RDS1cnQEAAA=="
},
"Metadata": {
"aws:cdk:path": "AgentClawStack/CDKMetadata/Default"
@@ -823,6 +778,25 @@
}
},
"Outputs": {
"WorkspaceMcpFunctionUrl": {
"Description": "workspace-mcp Lambda Function URL (MCP endpoint for Gmail/Calendar)",
"Value": "https://25hugrzw4uwtueeg77jsmft6lq0wunmd.lambda-url.us-east-1.on.aws"
},
"GoogleCredentialsSecretArn": {
"Description": "Google OAuth user credentials secret ARN",
"Value": {
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":secretsmanager:us-east-1:495395224548:secret:agent-claw/google-workspace-credentials"
]
]
}
},
"WebhookUrl": {
"Description": "Register this URL with Telegram BotFather as webhook endpoint",
"Value": {
@@ -844,9 +818,7 @@
},
"WorkspaceBucketName": {
"Description": "S3 bucket containing agent workspace files",
"Value": {
"Ref": "WorkspaceBucket53E30B92"
}
"Value": "agent-claw-workspace-495395224548"
},
"SessionTableName": {
"Description": "DynamoDB table for session mapping",
@@ -854,6 +826,12 @@
"Ref": "SessionStore8C86EEFE"
}
},
"UsersTableName": {
"Description": "DynamoDB user registry table",
"Value": {
"Ref": "UsersTable9725E9C8"
}
},
"MessageQueueUrl": {
"Description": "SQS FIFO queue for incoming messages",
"Value": {