Skip to content

singledigit/event-driven-agents

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

10 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Content Prep Agent

An event-driven content preparation pipeline for long-form video production. Orchestrates five phases β€” research, outline, demo code, script draft, and script edit β€” through a Lambda Durable Function with human-in-the-loop review via Telegram.

Architecture

graph TD
    TG[Telegram] --> APIGW[API Gateway]
    APIGW --> TBot[Telegram Bot<br/>Lambda]
    TBot --> Orch[Orchestrator<br/>Lambda Durable Function]
    Orch --> RA[Research Agent<br/>Lambda]
    Orch --> OA[Outline Agent<br/>Lambda]
    Orch --> DCA[Demo Code Agent<br/>Lambda]
    Orch --> SDA[Script Draft Agent<br/>Lambda]
    Orch --> SEA[Script Edit Agent<br/>Lambda]
    RA --> BR[Amazon Bedrock]
    OA --> BR
    DCA --> BR
    SDA --> BR
    SEA --> BR
    Orch --> BR
    Orch --> TG

    TBot --> DDB[(DynamoDB<br/>CallbackMappingTable)]
    Orch --> DDB
    Orch --> S3[(S3<br/>ArtifactBucket)]
Loading

All Lambdas run on ARM64 with Python 3.14 and share a Strands Agents layer. The orchestrator is a Lambda Durable Function β€” it sleeps at zero compute cost while waiting for agent callbacks and human approvals (up to 30 days).

Workflow

sequenceDiagram
    participant U as User (Telegram)
    participant TB as Telegram Bot Lambda
    participant O as Orchestrator (Durable)
    participant A as Agent Lambda

    U->>TB: Send topic message
    TB->>O: Async invoke (Event)
    TB->>U: "Starting pipeline..."

    Note over O: classify_task_step
    Note over O: create_pipeline_run_step
    O->>U: Pipeline started

    Note over O: wait_for_callback (approval-mode-choice)
    O->>U: "Review Each Phase" or "Approve All" buttons
    Note over O: πŸ’€ Sleeping (zero compute)

    alt Review Each Phase
        U->>TB: Tap πŸ” Review Each Phase
        TB->>O: callback_success ({mode: review})
        O->>U: "πŸ” Review mode enabled"
    else Approve All
        U->>TB: Tap βœ… Approve All
        TB->>O: callback_success ({mode: auto})
        O->>U: "⏩ Auto-approve enabled"
    end

    loop For each phase (research β†’ outline β†’ demo_code β†’ script_draft β†’ script_edit)
        Note over O: wait_for_callback ({phase}/agent)
        O->>A: Async invoke (Event) + callback_id
        Note over O: πŸ’€ Sleeping (zero compute)
        Note over A: Bedrock call + parse output
        A->>O: send_durable_execution_callback_success (result)
        Note over O: Resumes

        alt Auto-approve mode
            O->>U: Phase output (no buttons) + "⏩ auto-approved"
            Note over O: Moves to next phase immediately
        else Review mode
            Note over O: wait_for_callback ({phase}/review)
            O->>U: Phase output + Approve/Iterate/Go Back buttons
            Note over O: πŸ’€ Sleeping (zero compute)

            alt Approve
                U->>TB: Tap βœ… Approve
                TB->>O: callback_success ({action: approve})
                O->>U: "βœ… Phase approved. Starting next..."
            else Iterate
                U->>TB: Tap πŸ”„ Iterate + send feedback
                TB->>O: callback_success ({action: iterate, feedback})
                O->>U: "πŸ”„ Revising with your feedback..."
                Note over O: wait_for_callback ({phase}/agent-revision-N)
                O->>A: Async invoke + feedback + callback_id
                A->>O: callback_success (revised result)
                Note over O: Back to review loop
            else Go Back
                U->>TB: Tap ⬅️ Go Back
                TB->>O: callback_success ({action: go_back, target_phase})
                Note over O: Jump to target phase
            end
        end
    end

    O->>U: "πŸŽ‰ All phases complete!"
Loading

The orchestrator never blocks waiting for agents. Each agent is invoked asynchronously and calls back the durable execution when done. Human review gates also use wait_for_callback with a 30-day timeout. The orchestrator consumes zero compute while waiting.

Pipeline Flow

Before any phase runs, the user chooses an approval mode:

  • Review Each Phase: approve/iterate/go-back after every phase (default interactive experience)
  • Approve All: all phases run back-to-back with results sent to Telegram but no approval gates

Then for each phase (research β†’ outline β†’ demo_code β†’ script_draft β†’ script_edit):

  1. Orchestrator enters wait_for_callback and fires the agent Lambda async with the callback ID
  2. Agent Lambda runs its Bedrock call, parses the result
  3. Agent Lambda calls send_durable_execution_callback_success with the output
  4. Orchestrator resumes and uploads the artifact to S3
  5. If auto-approve: sends output to Telegram without buttons, moves to next phase
  6. If review mode: sends output with Approve / Iterate / Go Back buttons, waits for user
    • Approve: stores output, notifies user, moves to next phase
    • Iterate: notifies user, re-invokes agent with feedback
    • Go Back: jumps to a previous phase

Durable Execution Steps

The orchestrator names every durable operation for traceability:

Step Name Type Description
classify_task_step @durable_step Classify user message via Haiku 4.5
create_pipeline_run_step @durable_step Create run ID and initial state
send_telegram_step @durable_step All Telegram notifications
send_telegram_info_step @durable_step Phase output without buttons (auto-approve)
approval-mode-choice wait_for_callback Upfront review vs auto-approve choice
{phase}/agent wait_for_callback Async agent invocation
{phase}/agent-revision-{n} wait_for_callback Agent re-invocation after feedback
{phase}/review wait_for_callback Human review gate (review mode only)
{phase}/review-{n} wait_for_callback Review after nth revision

Prerequisites

  • AWS CLI configured with credentials (via default profile, named profile, or environment variables)
  • AWS SAM CLI (v1.153.1+)
  • Python 3.14
  • Bedrock cross-region inference profile access enabled for:
    • us.anthropic.claude-haiku-4-5-20251001-v1:0 (task routing)
    • us.anthropic.claude-sonnet-4-20250514-v1:0 (outline, script draft)
    • us.anthropic.claude-sonnet-4-5-20250929-v1:0 (research, demo code, script edit)

Setup

1. Create a Telegram Bot

  1. Open Telegram and search for @BotFather
  2. Send /newbot and follow the prompts to name your bot
  3. BotFather will give you a token β€” save this

2. Store Secrets in Secrets Manager

Store the bot token:

aws secretsmanager create-secret \
  --name content-prep-agent/telegram-bot-token \
  --secret-string "YOUR_BOT_TOKEN"

Generate and store a webhook secret. This is a shared secret between Telegram and your bot Lambda β€” Telegram sends it in the X-Telegram-Bot-Api-Secret-Token header on every webhook request, and the Lambda rejects anything that doesn't match. Pick any string you like (1–256 characters, alphanumeric and _-):

WEBHOOK_SECRET=$(openssl rand -hex 32)

aws secretsmanager create-secret \
  --name content-prep-agent/telegram-webhook-secret \
  --secret-string "$WEBHOOK_SECRET"

3. Deploy the Stack

sam build
sam deploy --guided

4. Register the Telegram Webhook

BOT_TOKEN=$(aws secretsmanager get-secret-value \
  --secret-id content-prep-agent/telegram-bot-token \
  --query SecretString --output text)

WEBHOOK_URL=$(aws cloudformation describe-stacks \
  --stack-name content-prep-agent \
  --query 'Stacks[0].Outputs[?OutputKey==`WebhookUrl`].OutputValue' \
  --output text)

WEBHOOK_SECRET=$(aws secretsmanager get-secret-value \
  --secret-id content-prep-agent/telegram-webhook-secret \
  --query SecretString --output text)

curl -X POST "https://api.telegram.org/bot${BOT_TOKEN}/setWebhook" \
  -H "Content-Type: application/json" \
  -d "{\"url\": \"${WEBHOOK_URL}\", \"secret_token\": \"${WEBHOOK_SECRET}\"}"

The secret_token tells Telegram to include an X-Telegram-Bot-Api-Secret-Token header on every webhook request. The bot Lambda checks this header and rejects requests that don't match β€” so random HTTP traffic hitting your API Gateway endpoint gets a 403 instead of being processed.

5. Add Allowed Telegram Users

The bot uses username-based authorization. Only users registered in the DynamoDB table can interact with the bot. To add a user:

TABLE_NAME=$(aws cloudformation describe-stacks \
  --stack-name content-prep-agent \
  --query 'Stacks[0].Outputs[?OutputKey==`CallbackMappingTableName`].OutputValue' \
  --output text)

aws dynamodb put-item \
  --table-name "$TABLE_NAME" \
  --item '{"short_id": {"S": "user#yourtelegramusername"}}'

Replace yourtelegramusername with the Telegram username (lowercase, no @ prefix).

To remove a user:

aws dynamodb delete-item \
  --table-name "$TABLE_NAME" \
  --key '{"short_id": {"S": "user#yourtelegramusername"}}'

Usage

  1. Send a topic to your Telegram bot
  2. Choose approval mode: "Review Each Phase" for interactive control, or "Approve All" to let it run unattended
  3. If review mode: each phase shows results with Approve / Iterate / Go Back buttons
  4. If auto-approve: phases run back-to-back, results are sent to Telegram without buttons
  5. After the final phase, the pipeline completes

You can also reply directly to any phase output message with feedback text (review mode).

Project Structure

β”œβ”€β”€ template.yaml                 # SAM template
β”œβ”€β”€ samconfig.toml                # SAM config
β”œβ”€β”€ layers/strands/               # Shared strands-agents layer
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ orchestrator/             # Durable function orchestrator
β”‚   β”‚   β”œβ”€β”€ app.py                # Pipeline handler + durable steps
β”‚   β”‚   β”œβ”€β”€ task_router.py        # Topic classification (Haiku 4.5)
β”‚   β”‚   β”œβ”€β”€ phase_runner.py       # Async Lambda invocation
β”‚   β”‚   β”œβ”€β”€ telegram_notifier.py  # Send outputs + callback buttons
β”‚   β”‚   β”œβ”€β”€ artifact_store.py     # S3 artifact upload + presigned URLs
β”‚   β”‚   β”œβ”€β”€ models.py             # Shared data models
β”‚   β”‚   └── requirements.txt
β”‚   β”œβ”€β”€ telegram-bot/             # Webhook handler
β”‚   β”œβ”€β”€ research-agent/           # Lambda (Sonnet 4.5)
β”‚   β”œβ”€β”€ outline-agent/            # Lambda (Sonnet 4)
β”‚   β”œβ”€β”€ demo-code-agent/          # Lambda (Sonnet 4.5)
β”‚   β”œβ”€β”€ script-draft-agent/       # Lambda (Sonnet 4)
β”‚   └── script-edit-agent/        # Lambda (Sonnet 4.5)

Configuration

Configure your AWS profile and region in samconfig.toml.

Two secrets are resolved at deploy time from Secrets Manager:

  • content-prep-agent/telegram-bot-token β€” the bot token from BotFather
  • content-prep-agent/telegram-webhook-secret β€” shared secret for webhook request validation

Troubleshooting

Bot doesn't respond: Check the webhook β€” curl https://api.telegram.org/bot${BOT_TOKEN}/getWebhookInfo

Getting 403 Forbidden: The bot Lambda validates the X-Telegram-Bot-Api-Secret-Token header on every request. If you see 403s, make sure the secret_token you passed to setWebhook matches the value in Secrets Manager (content-prep-agent/telegram-webhook-secret).

Agent timeout: Agents have a 15-minute Lambda timeout and 10-minute Bedrock read timeout. If an agent fails, the orchestrator's wait_for_callback times out after 15 minutes and reports failure to Telegram.

Bedrock access denied: Enable model access in the Bedrock console for us-west-2.

Durable function issues: The orchestrator uses AutoPublishAlias: live. Durable functions require qualified ARNs β€” the template handles this automatically.

About

Demo of event driven agents using durable functions

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages