# empctl: Your Gateway to Emp Cloud

Emp Cloud is a permissionless cloud platform optimized for web3 applications. It provides production-ready infrastructure including automated scaling, monitoring, and web3-specific integrations, saving development teams hundreds of hours of setup and maintenance time.

Our platform offers an opinionated but flexible infrastructure stack, carefully designed to accelerate your project without distracting you from what matters most, your product.

![Emp Cloud](/files/uFvS9NM0xFcysKNJUOom)

Emp Cloud also includes native web3 AI Agent tooling to simplify interactions with the Twitter API and onchain data.

## Getting started with empctl

`empctl` is a CLI tool for creating and managing your Emp Cloud projects.

Install `empctl` through Homebrew:

```shell
brew tap empyrealapp/homebrew-empyrealsdk
brew install empctl
```

Register your first project:

```shell
empctl register --name my-project --email my-email@example.com
```

Once registered, your Emp Cloud project provides access to a secure, isolated infrastructure including:

* DynamoDB table for fast, scalable data storage
* S3 bucket for object storage
* SQS FIFO queue for reliable message processing
* Secrets management
* Private ECR repositories
* Managed deployments
* Centralized logging

Each project maintains strict isolation - all resources are dedicated to your project and created on-demand.

To deploy your first application, create a Dockerfile that demonstrates a basic web3 interaction:

```dockerfile
FROM alpine:latest
CMD ["sh", "-c", "while true; do echo 'Logging...'; sleep 1; done"]
```

```shell
empctl services init --name myservice
empctl build --push --deploy --tag v1 --service myservice
empctl deployments logs --service myservice
```

Congratulations! You've deployed your first application to Emp Cloud. Continue to [write an AI agent](/02-write-a-simple-ai-agent) guide to learn about Twitter integrations, AI agents, and more.


# Write a simple AI agent

In 60 lines of code, you can write a simple AI agent that can reply to tweets with a poem.\
It relies on Emp Cloud and two libraries from Empyreal, `emp-agents` and `emp_hooks`.\
The agent replies to tweets that mention it by name, in this case `@empcloud_demo`.

Start by collecting your Twitter API keys and OpenAI API key and save them in a `.env` file.

```.env
TWITTER_BEARER_TOKEN=...
TWITTER_CONSUMER_KEY=...
TWITTER_CONSUMER_SECRET=...
TWITTER_ACCESS_TOKEN=...
TWITTER_ACCESS_TOKEN_SECRET=...
OPENAI_API_KEY=...
```

Upload the secrets to your project

```
empctl secrets upsert --file .env
```

Write the code for the agent.\
Make sure to have fun with the prompt, as your agent's personality is defined by it.

```python
import asyncio
import json
import os
import sys

from tweepy import Tweet
from tweepy.client import Client

from emp_hooks import twitter
from emp_hooks.hook_manager import hooks
from emp_agents import AgentBase
from emp_agents.providers import OpenAIProvider, OpenAIModelType

my_agent_twitter_id = "1902885085294235650"

@twitter.on_tweet("@empcloud_demo")
def on_tweet(tweet: Tweet):
    print("RECEIVED TWEET")
    data = json.loads(tweet["data"])

    tweet_id = data["id"]
    author_id = int(data["author_id"])

    # ignore if the post is from the bot itself
    if author_id == my_agent_twitter_id:
        return

    print(f"Received tweet: {tweet_id} from {author_id}")
    sys.stdout.flush()

    client = Client(
        bearer_token=os.environ["TWITTER_BEARER_TOKEN"],
        consumer_key=os.environ["TWITTER_CONSUMER_KEY"],
        consumer_secret=os.environ["TWITTER_CONSUMER_SECRET"],
        access_token=os.environ["TWITTER_ACCESS_TOKEN"],
        access_token_secret=os.environ["TWITTER_ACCESS_TOKEN_SECRET"],
    )

    agent = AgentBase(
        prompt="""
            You are a helpful agent. You will respond in 200 characters to any questions, in a mechanical way.
            You will respond as if you are an extra in a film. Make sure you be kind of depressing and reference
            famous poetry in a really pretentious way when you get the chance.
        """,
        provider=OpenAIProvider(
            api_key=os.environ["OPENAI_API_KEY"],
            default_model=OpenAIModelType.gpt4o_mini,
        )
    )

    agent_response = agent.answer(data["text"])
    response = asyncio.run(agent_response)

    try:
        response = client.create_tweet(
            text=response,
            in_reply_to_tweet_id=tweet_id,
        )
        print(f"Tweeted: {response.data['text']}")
    except Exception as e:
        print(f"Error Tweeting: {e}")
        sys.stdout.flush()


hooks.run(keep_alive=True)
```

```requirements.txt
emp-agents
emp-hooks
```

Lastly, you need a Dockerfile to build and deploy the agent.

```dockerfile
FROM python:3.12-alpine

WORKDIR /app

COPY . .
RUN python -m pip install -r requirements.txt

CMD ["python", "demo.py"]
```

Build and deploy the agent

```shell
empctl build --push --deploy --service myservice
```

Congratulations! You just deployed your first AI agent.

### Wondering how the tweet arrives at the agent?

The flow involves the `emp_hooks` library and custom k8s controllers that are watching all the events on the projects and interact with the Twitter API.

Tweets are being read from the Twitter API Stream and then pushed to the project's SQS.fifo queue, which then is read from the `emp_hooks` library again and passed to the agent.


# Accessing the infrastructure

The infrastructure components are provisioned on the background for each of the projects.\
Any container/pod that is running on the project's namespace will have access to the infrastructure components.

Our platform is language agnostic. We provide a lightweight library called `emp_hooks` in python that simplifies interacting with infrastructure components but is not at all required or needed to use the platform.

The aws libraries are built to automatically pick up the available credentials from the environment.

For example, the following snippet will access the DynamoDB table and write and read from it.

```python
import boto3
import os

def main():
    region_name = os.getenv('AWS_REGION')
    table_name = os.getenv('AWS_DYNAMODB_TABLE_NAME')
    
    dynamodb = boto3.resource("dynamodb", region_name=region_name)
    table = dynamodb.Table(table_name)

    test_item = {
        "id": "test-id-1",
        "Data": "Hello from the project!"
    }
    table.put_item(Item=test_item)
    print(f"Successfully wrote item to {table_name}: {test_item}")

    response = table.get_item(Key={"id": "test-id-1"})
    item = response.get("Item")
    if item:
        print(f"Successfully read item from {table_name}: {item}")
    else:
        print(f"No item found in {table_name}")

if __name__ == "__main__":
    main()
```

The following infrastructure-related environment variables are automatically injected into each pod:

**DynamoDB Related:**

* `AWS_DYNAMODB_TABLE_ARN` - The ARN of the DynamoDB table
* `AWS_DYNAMODB_TABLE_NAME` - The name of the DynamoDB table

**ECR Related:**

* `AWS_ECR_REPO_URI` - The URI of the ECR repository

**S3 Related:**

* `AWS_S3_BUCKET_ARN` - The ARN of the S3 bucket
* `AWS_S3_BUCKET_NAME` - The name of the S3 bucket

**Secrets Related:**

* `AWS_SECRET_ARN` - The ARN of the AWS Secret
* `AWS_SECRET_NAME` - The name of the AWS Secret

**SQS Related:**

* `AWS_SQS_QUEUE_ARN` - The ARN of the SQS queue
* `AWS_SQS_QUEUE_NAME` - The name of the SQS queue

**Region Configuration:**

* `AWS_REGION` - The AWS region (default: us-east-2)
* `AWS_DEFAULT_REGION` - The AWS default region (default: us-east-2)

**Persistent Volume (EFS) Related:**

* `FILESYSTEM_PATH` - The path to the EFS file system
* `DEPLOYMENT_FILESYSTEM_PATH` - The path for the deployment to use the EFS file system

**Environment:**

* `ENVIRONMENT` - The deployment environment (e.g., production)


# Understanding the internals of Emp Cloud

Emp Cloud is a platform for building and deploying AI agents and web3 applications.

It is an abstraction layer on top of AWS infrastructure and kubernetes.\
We have developed multiple kubernetes controllers and CRDs to automate and manage the provisioning, lifecycle and perimeter of the infrastructure.

We currently build on the following open source operators:

* [AWS Operator](https://github.com/aws-controllers-k8s/aws-operator)
* [AWS ACK S3](https://github.com/aws-controllers-k8s/s3-controller)
* [AWS ACK Secrets Manager](https://github.com/aws-controllers-k8s/secretsmanager-controller)
* [AWS ACK ECR](https://github.com/aws-controllers-k8s/ecr-controller)
* [AWS ACK IAM](https://github.com/aws-controllers-k8s/iam-controller)
* [AWS ACK DynamoDB](https://github.com/aws-controllers-k8s/dynamodb-controller)
* [AWS ACK SQS](https://github.com/aws-controllers-k8s/sqs-controller)
* [AWS ACK EFS](https://github.com/aws-controllers-k8s/efs-controller)
* [AWS EFS CSI](https://github.com/kubernetes-sigs/aws-efs-csi-driver)
* [Istio Operator](https://github.com/istio/istio-operator)
* [AWS Load Balancer Controller](https://github.com/kubernetes-sigs/aws-load-balancer-controller)
* [External Secrets Operator](https://github.com/external-secrets/kubernetes-external-secrets)

## What happens when you create a new project?

Creating a new project through `empctl` interacts with a series of custom k8s resources that expand into multiple AWS resources on the fly.

The reconciliation logic is implemented as a series of controllers that watch for changes in the custom resources and update the corresponding AWS resources.

The resources are available almost instantly and the will be cleaned up when the project is deleted.

## Understanding the security model

To minimize the attack surface of our infrastructure, users do not have access to the AWS credentials or resources directly.

The provisioning of infrastructure components also creates the necessary IAM roles and policies for them to be accessed only by the project's namespace and the containers that are running on it.

On more complex occasions, like accessing an ECR token to push a docker image or updating secrets we have developed a flow that involves triggering jobs in the namespace to do these operations.

So, for example, for accessing an ECR login token, first `empctl` will create a job in the namespace that will interact with the ECR API and get the login token, then the token will be forwarded to the user's local machine or CI/CD system to be used for pushing the actual image. The process is seamless and doesn't require any additional configuration or elevated permission system.

![ECR Token flow](/files/5DMYd5X0JtgnvDOYm6sd)

On the more complex scenario of updating secrets securely, to avoid exposing credentials or MITM attacks, the flow involves `empctl` uploading the secrets to a presigned URL and then triggering a job in the namespace that accesses the URL and updates the secrets on the AWS Secret Manager secret. Additionally, the actual secrets are being fetched on the cluster securely through the `External Secrets Operator` and stored in a k8s secret that is mounted on the pods running on the namespace.


# Managing multiple services

Create new services with

```shell
empctl services init --name string
```

Upon deployment, the services can interact with each other using the service name as the host, making it easy to build complex applications with multiple services.

```python
import requests

response = requests.get("http://service-name:8000/")
```

## Ingress

Services can be exposed to the public internet through a managed Ingress that is not available on the Free Tier.


# Emp Cloud Pricing

Currently, Emp Cloud is in beta and comes with a free tier. Users can create projects and deploy services to them, which will be complete but run with limited cpu and memory resources. Inactive projects will be deleted after 14 days of inactivity.

## Compute Units

Emp Cloud uses a compute unit model to charge for the resources used by services. Each service is allocated a certain number of compute units, which are used to determine the resources available to the service. Users can purchase additional compute units and use them to allocate more resources to their services.

More details will be available soon.


# empctl cheatsheet

## register

Register a new project

```
empctl register --name my-project --email my-email@example.com
```

## services

Create a new service

```
empctl services init --name string
```

Get the list of services

```
empctl services get
```

Delete a service

```
empctl services delete --name string
```

## build

Build, deploy and push a multiplatform image

```
empctl build --push --deploy --tag string --file Dockerfile --multi-platform --service string
```

## configs

Get the current configs and infrastructure

```
empctl configs get
```

Change API token on your local config

```
empctl configs set-token string
```

## deployments

Inspect the deployment

```
empctl deployments get
```

Get the logs

```
empctl deployments logs --service string
```

## ecr

Get the ECR repository

```
empctl ecr token --service string
```

## secrets

Update key/value

```
empctl secrets upsert --name my-secret --val my-secret-value
```

Update json file

```
empctl secrets upsert --file secrets.json
```

Update from env file

```
empctl secrets upsert --file .env
```


