AWS DevOps & Developer Productivity Blog

Building a Slack-powered AI development agent with Kiro CLI and headless authentication

Every code review discussion, incident response thread, and standup happens in Slack. But when an engineer needs to analyze a service or debug a failing test, they leave Slack, open a terminal, navigate to the repository, run commands, and paste the output back. That round trip takes 30 seconds for someone who knows exactly where to look and 5 minutes for someone less familiar with the codebase. Across a team of 10 engineers doing this 15 times a day, that adds up to over 12 hours of lost engineering time per week.

This post walks through building a ChatOps integration that runs Kiro CLI from a Slack slash command. An engineer types /kiro analyze auth-service for memory leaks, and the results appear directly in the channel—no context switch required. The solution uses AWS Lambda, Amazon API Gateway, and AWS Secrets Manager, and it depends on Kiro CLI’s headless authentication to run without an interactive session.

In this post, you will learn how to:

  • Configure a Slack App with a slash command that triggers an AWS Lambda function
  • Authenticate Kiro CLI in a headless environment using API key-based authentication
  • Build and deploy a container image with Kiro CLI to Amazon Elastic Container Registry (Amazon ECR)
  • Deploy the full solution with AWS Serverless Application Model (AWS SAM)

Why headless authentication matters

A Slack slash command triggers a webhook. The webhook invokes a Lambda function. The Lambda function runs Kiro CLI. At no point in this chain is there a browser, a terminal, or a human session.

Without headless authentication, this architecture does not work. Kiro CLI would require an interactive login, and a Lambda function has no display and no way to complete an OAuth flow.

With an API key, Kiro CLI authenticates silently:

export KIRO_API_KEY=ksk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

kiro-cli chat --no-interactive "analyze auth-service for memory leaks"

The API key is stored in AWS Secrets Manager, fetched at runtime, and injected into the Lambda environment. The engineer in Slack never sees or manages the key.

Important: API key-based authentication is available for Kiro Pro, Pro+, and Power subscribers. If your subscription is managed by an administrator, your Kiro admin must enable API key authentication first. For details, see API key governance.

Architecture overview

architecture overview

The solution consists of two Lambda functions, an API Gateway endpoint, and AWS Secrets Manager. The request and response follow two separate paths:

  • Request path: Slack → API Gateway → Dispatcher Lambda → acknowledge back to Slack (under 3 seconds), then async invoke → Worker Lambda
  • Response path: Worker Lambda → Slack response_url (direct HTTPS POST, bypasses API Gateway)

Why two Lambda functions?

Slack requires a response within 3 seconds of a slash command. Kiro CLI analysis takes 10–60 seconds depending on the repository size and prompt complexity. The Dispatcher acknowledges the command immediately and invokes the Worker asynchronously. The Worker runs Kiro CLI and posts results back to Slack through the response_url provided in the original payload. This is a standard pattern for Slack integrations that perform long-running work.

A note on response_url limits: the webhook Slack provides in the slash command payload expires 30 minutes after the command is issued and accepts a maximum of 5 responses. The 10-minute Worker timeout and single response in this solution stay well inside both limits. If you raise the Lambda timeout beyond 30 minutes or add incremental progress updates, these POSTs begin to fail silently – switch to chat.postMessage with a bot token at that point.

Prerequisites

  • Before you begin, you need the following:
  • An AWS account with permissions to create Lambda functions, API Gateway, Amazon ECR repositories, Secrets Manager secrets, and IAM roles
  • An infrastructure-as-code tool for deploying serverless resources (this post uses AWS SAM CLI, but you can adapt the templates to AWS CDK, AWS CloudFormation, Terraform, or your preferred tool)
  • Finch or Docker installed for building container images
  • A Slack workspace where you have permission to create a Slack App
  • A Kiro Pro, Pro+, or Power subscription with API key authentication enabled

Step 1: Gather credentials

You need three credentials before deploying. Collect all of them first, then store them in Secrets Manager in Step 2.

Kiro API key
This authenticates Kiro CLI in headless mode.

  1. Sign in to app.kiro.dev
  2. Navigate to API Keys
  3. Create a new key named kiro-chatops
  4. Copy the key (starts with ksk_) – it is shown only once

get Kiro API key

Slack Signing Secret – This allows the Dispatcher to verify that incoming requests originate from Slack.

  1. Go to api.slack.com/apps and click Create New App → From scratch
  2. Name it Kiro Agent and select your workspace
  3. On the Basic Information page, scroll to App Credentials
  4. Copy the Signing Secret (32-character hex string)

create a slack app

Slack Bot Token – Optional
The Worker posts results using the response_url from the original slash command payload, which is a pre-authenticated webhook that does not require a bot token. Collect a bot token with the chat:write scope only if you extend the solution to post messages independently of a slash command response.

  1. In your Slack App settings, go to OAuth & Permissions
  2. Add the Bot Token Scope: chat:write
  3. Choose Install to Workspace and authorize
  4. Copy the Bot User OAuth Token (starts with xoxb-)

set up slack oauth token

While you are in the Slack App settings, also configure the slash command:

  1. Go to Slash Commands → Create New Command
  2. Set Command to /kiro
  3. Set Request URL to https://placeholder (update after deployment in Step 6)
  4. Set Short Description to Run Kiro-CLI development tasks
  5. Set Usage Hint to [analyze|review|debug|explain] <description>

define slack commands

Step 2: Store secrets in AWS Secrets Manager

Store each credential as a separate secret. The Lambda functions retrieve these at runtime using IAM-scoped access.

aws secretsmanager create-secret \
  --name kiro-chatops/kiro-api-key \
  --secret-string "<your-kiro-api-key>" \
  --region us-east-1

aws secretsmanager create-secret \
  --name kiro-chatops/slack-signing-secret \
  --secret-string "<your-signing-secret>" \
  --region us-east-1

aws secretsmanager create-secret \
  --name kiro-chatops/git-token \
  --secret-string "<your-github-pat>" \
  --region us-east-1

If your target repository is private, also store a GitHub Personal Access Token with repo scope. The Worker uses this token to clone the repository inside the Lambda execution environment.

Verify the secrets were created:

aws secretsmanager list-secrets \
  --filter Key="name",Values="kiro-chatops" \
  --query "SecretList[].Name" \
  --output table \
  --region us-east-1

Step 3: Build the Dispatcher Lambda

The Dispatcher has three responsibilities: verify that the request came from Slack, acknowledge the slash command within 3 seconds, and invoke the Worker asynchronously.

Request verification – Slack signs every request with HMAC-SHA256 using your app’s signing secret. The Dispatcher must validate this signature before processing payloads. The verification logic constructs a base string from the request timestamp and body, computes the HMAC, and compares it to the signature in the request header:

sig_basestring = f"v0:{timestamp}:{body}"
expected = "v0=" + hmac.new(
    signing_secret.encode(), sig_basestring.encode(), hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)

Reject any request with a timestamp older than 5 minutes to prevent replay attacks. Normalize request headers to lowercase before reading them—API Gateway may preserve the original casing from the client.

Async handoff
After verifying the request, parse the slash command payload to extract text, user_name, and response_url. Then invoke the Worker Lambda with InvocationType="Event" (fire-and-forget) and immediately return an acknowledgment to Slack:

lambda_client.invoke(
   FunctionName=os.environ["WORKER_FUNCTION_NAME"],
    InvocationType="Event",
    Payload=json.dumps({
        "command_text": command_text,
        "user_name": user_name,
        "response_url": response_url,
    })
)

If the user sends /kiro with no arguments, return an ephemeral usage message with examples. The Dispatcher uses the standard Python 3.12 Lambda runtime and requires no container image.

Step 4: Build the Worker Lambda container image

The Worker runs Kiro CLI against a cloned repository and posts results to Slack. Because Kiro CLI depends on git, system libraries (NSS, X11, ALSA), and a binary that exceeds Lambda’s 250 MB layer limit, package the Worker as a container image.

Dockerfile structure
Start from the AWS Lambda Python 3.12 base image. Install git and the shared libraries that Kiro CLI requires, then install Kiro CLI itself:

FROM public.ecr.aws/lambda/python:3.12
RUN dnf install -y git unzip alsa-lib atk at-spi2-atk cups-libs \
    libdrm libXcomposite libXdamage libXrandr mesa-libgbm \
    pango nss nspr libXtst && dnf clean all
RUN curl -fsSL https://cli.kiro.dev/install | bash && \
    cp /root/.local/bin/kiro-cli* /usr/local/bin/ && \
    chmod +x /usr/local/bin/kiro-cli*
COPY index.py ${LAMBDA_TASK_ROOT}/
RUN pip install boto3 --target "${LAMBDA_TASK_ROOT}"
CMD ["index.handler"]

Two details matter here. First, copy the Kiro CLI binary to /usr/local/bin/ rather than leaving it in /root/.local/bin/—Lambda runs as a non-root user that cannot access /root/. Second, build with --platform linux/amd64 regardless of your local architecture, because Lambda defaults to x86_64.

Worker logic – The handler performs four steps:

  1. Fetch the Kiro API key (and optionally a Git token) from Secrets Manager
  2. Clone the repository to /tmp/repo using git clone --depth 1
  3. Run kiro-cli chat --no-interactive "<prompt>" with KIRO_API_KEY and HOME=/tmp set in the environment
  4. Post the output to Slack via the response_url

Setting HOME=/tmp is required because Kiro CLI writes a session database, and Lambda’s filesystem is read-only except for /tmp. Strip ANSI escape codes from the output before posting—Kiro CLI emits terminal colors that render as garbage in Slack.

The subprocess timeout should be shorter than the Lambda timeout to allow time for error handling and the Slack POST. Set the subprocess timeout explicitly to 540 seconds in the Worker code, rather than relying on the Lambda timeout alone. A 9-minute subprocess limit with a 10-minute Lambda timeout provides a 1-minute buffer.

Truncate output to 3,800 characters before posting. Slack’s message limit is 4,000 characters per block, and the surrounding formatting consumes part of that space.

Build and push to Amazon ECR

Clean up /tmp/repo at the end of every invocation. Lambda may reuse a warm execution environment, so anything left in /tmp persists into the next invocation. Removing the clone in a finally block helps prevent one user’s repository from leaking into a later request and keeps the 512 MB ephemeral storage from filling up across warm invocations.

AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text) 
AWS_REGION=us-east-1 
 # Create the ECR repository (first time only) 
aws ecr create-repository --repository-name kiro-worker --region $AWS_REGION 
 # Authenticate to ECR 
aws ecr get-login-password --region $AWS_REGION | \ 
  finch login --username AWS --password-stdin \ 
  $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com 
 # Build for the correct architecture 
cd worker 
finch build --platform linux/amd64 -t kiro-worker:latest . 
 # Tag and push 
finch tag kiro-worker:latest \ 
  $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/kiro-worker:latest 
finch push \ 
  $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/kiro-worker:latest

Step 5: Deploy with AWS SAM

The SAM template defines both Lambda functions, the API Gateway endpoint, and the IAM policies. The Dispatcher uses a standard Python runtime. The Worker references the container image you pushed to Amazon ECR.

Key resource configuration:

Resource Runtime Timeout Memory Package type
Dispatcher Python 3.12 10 s 256 MB Zip
Worker Container 600 s (10 min) 1024 MB Image

Both functions use AWSSecretsManagerGetSecretValuePolicy scoped to the kiro-chatops/* secret prefix. The Dispatcher also gets LambdaInvokePolicy for the Worker function. Neither function has broader AWS permissions.

The SAM template accepts the ECR image URI as a parameter:

Parameters: 
  EcrImageUri: 
    Type: String 
    Description: ECR image URI for the Worker Lambda 
 
Resources: 
  WorkerFunction: 
    Type: AWS::Serverless::Function 
    Properties: 
      PackageType: Image 
      ImageUri: !Ref EcrImageUri 
      Timeout: 600 
      MemorySize: 1024

Deploy:

cd ..  # Back to the project root where template.yaml lives 
sam build 
sam deploy --guided \ 
  --stack-name kiro-chatops \ 
  --parameter-overrides \ 
    EcrImageUri=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/kiro-worker:latest

SAM prompts you to confirm IAM role creation and acknowledge that the Dispatcher has no authentication (request verification happens in code via the Slack signing secret). After deployment completes, note the ApiEndpoint output value.

Step 6: Connect Slack to the endpoint

  1. Go to api.slack.com/apps and select your Kiro Agent app
  2. Navigate to Slash Commands and edit /kiro
  3. Replace the Request URL with the ApiEndpoint value from the SAM deployment output
  4. Choose Save

slack endpoint integration

Step 7: Test the integration

Test directly from Slack by typing in any channel where the app is installed:

test slack integration

/kiro analyze auth-service for memory leaks

Expected behavior:

  1. Slack immediately displays: “@yourname requested: analyze auth-service for memory leaks – Kiro is working on it…”
  2. After 15–60 seconds, the analysis results appear in the channel

expected Kiro agent behaviour

Kiro agent results

You can also invoke the Worker Lambda directly for testing without Slack:

aws lambda invoke \ 
  --function-name <WorkerFunctionName-from-SAM-output> \ 
  --invocation-type RequestResponse \ 
  --cli-binary-format raw-in-base64-out \ 
  --payload '{"command_text": "explain what this repo does", "user_name": "test", "response_url": "https://hooks.slack.com/YOUR/URL"}' \ 
  response.json

Sending /kiro with no arguments returns a usage help message.

Complete sample code can be found at aws-samples github repository – https://github.com/aws-samples/sample-kiro-chatops-slack-integration 

Practical slash command patterns

Once deployed, the value comes from the commands your team uses daily. These patterns map to real engineering workflows:

Category Example command
Code analysis /kiro analyze the payment module for error handling gaps
Code review /kiro review the last 3 commits on main for breaking changes
Debugging /kiro debug why the integration tests are failing
Knowledge /kiro explain how the authentication middleware works
Sprint support /kiro summarize all changes merged to main this week

The value compounds when results are visible to the entire channel. A junior engineer who might hesitate to open a CLI tool can type /kiro explain and get the same analysis and the rest of the team learns from it.

Extending the pattern

Multi-repository support – The basic implementation targets a single preconfigured repository. To support multiple repositories, parse a URL from the slash command text and clone it at runtime. This adds 5-15 seconds of latency and requires a Git token in Secrets Manager for private repositories.

Threaded responses – Post the acknowledgment as a channel message and the full results as a thread reply. This keeps the channel readable while preserving context for long analyses.

Approval workflows – For commands that modify code (for example, “create a PR that fixes this issue”), add a confirmation step. The Worker posts proposed changes with interactive buttons; the action executes only after explicit approval.

Audit logging – Log every invocation to Amazon DynamoDB: who ran it, what they asked, how long it took. This gives engineering leadership visibility into how the team uses AI-assisted development.

Constraints and trade-offs

Constraints:

  • Execution time – Lambda has a maximum 15-minute timeout. Complex analyses that exceed this will time out. The Worker is set to a 10-minute timeout with a 9-minute subprocess limit.
  • Ephemeral storage – The /tmp volume defaults to 512 MB. A shallow clone (–depth 1) strips Git history, but the working tree alone can exceed this for large monorepos or repositories with binary assets. You can increase ephemeral storage up to 10 GB by setting EphemeralStorage in the SAM template, or scope the clone to a subdirectory with –sparse-checkout for oversized repositories.
  • Slack message size – Each Block Kit text block is limited to 3,000 characters. Long outputs are truncated, with full results available in Amazon CloudWatch Logs.
  • Package size – Kiro CLI with its dependencies exceeds Lambda’s 250 MB layer limit. A container image (up to 10 GB) is required.

Trade-offs:

  • Lambda vs. Amazon ECS on AWS Fargate – Lambda is simpler and cheaper at the low-volume, bursty usage typical of a single team. Model your own break-even point with the AWS Pricing Calculator, since it shifts with average analysis duration and memory size. For high-volume teams, Fargate with a persistent container avoids cold starts. Start with Lambda and migrate if usage grows.
  • Public channel vs. ephemeral – Results are posted as in_channel (visible to everyone). For sensitive analyses, change response_type to ephemeral. Consider making this configurable per command.
  • Cost – Lambda compute is approximately $0.01-$0.05 per 10-minute execution at 1024 MB. The primary cost factor is Kiro CLI usage based on your subscription tier.

Security considerations

  • Request verification — The Dispatcher validates every request using HMAC-SHA256 with the Slack signing secret. Requests with timestamps older than 5 minutes are rejected.
  • Secrets management — Credentials are never hardcoded or stored in environment variables. They are fetched at runtime from Secrets Manager with IAM-scoped access.
  • Least-privilege IAM — The Dispatcher can only invoke the Worker and read secrets. The Worker can only read secrets. Neither has broader AWS permissions.
  • Audit trail — CloudWatch Logs capture every invocation including the command text, user, and Kiro CLI output. Enable AWS CloudTrail for API Gateway to track all incoming requests.

Cleaning up

To avoid ongoing charges, remove all resources when you are done testing:

sam delete --stack-name kiro-chatops 
 aws ecr delete-repository \ 
  --repository-name kiro-worker --force --region us-east-1 
 aws secretsmanager delete-secret \ 
  --secret-id kiro-chatops/kiro-api-key \ 
  --force-delete-without-recovery --region us-east-1 
aws secretsmanager delete-secret \ 
  --secret-id kiro-chatops/slack-signing-secret \ 
  --force-delete-without-recovery --region us-east-1 
aws secretsmanager delete-secret \ 
  --secret-id kiro-chatops/git-token \ 
  --force-delete-without-recovery --region us-east-1

To remove the Slack App, go to api.slack.com/apps, select Kiro Agent, and click Delete App.

Conclusion

This post demonstrated integrating Kiro CLI into Slack workflows using headless authentication, serverless functions, and secure credential management. The Dispatcher acknowledges instantly, the Worker runs Kiro CLI headless, and results appear in the channel where the team already communicates.

The architecture is deliberately simple – a slash command, an async handoff, and a container that runs a CLI tool. You can extend it with multi-repo support, threaded responses, or approval workflows as your team’s usage patterns emerge.

Start with a single slash command in one channel. The commands your team uses most will tell you where the friction was hiding.


About the authors

Vishal Karlupia
Vishal Karlupia is a Senior Technical Account Manager/Lead at Amazon Web Services, Chicago. He specializes in generative AI applications and helps customers build and scale their AI/ML workloads on AWS. Outside of work, he enjoys being outdoors and keeping bonfires alive.
Devi Nair
Devi Nair is a Technical Account Manager at Amazon Web Services, providing strategic guidance to enterprise customers as they build, operate, and optimize their workloads on AWS. She focuses on aligning cloud solutions with business objectives to drive long-term success and innovation.
Joanan Mpo
Joanan Mpo is a Technical Account Manager at AWS based in Montreal, Canada. He is helping enterprise financial services customers navigate the complexity of cloud at scale, bridging the gap between engineering teams and business outcomes.
Srinivas Ganapathi
Srinivas Ganapathi is a Principal Technical Account Manager at Amazon Web Services. He is based in Toronto, Canada, and works with games customers to run efficient workloads on AWS..