Imported from jthingelstad/librarian-thing (
pipeline/deploy/AGENTS.md). Install upstream withnpx skills add jthingelstad/librarian-thing --skill deploy. Copyright stays with the author.
pipeline/deploy/ — project memory
AWS deploy tooling for the Thingy Lambda stack. No README — this directory is operator-only.
The three scripts
| Script | What it does |
|---|---|
aws.py |
Packages the two Lambda bundles, optionally uploads all Librarian corpora, uploads code to S3, then runs CloudFormation update-stack with the new code keys + secrets from .env. The canonical deploy entrypoint. |
upload_corpus.py |
Builds the Weekly Thing corpus + graph from data/issues/*/archive.md, embeds via Bedrock Cohere, uploads to S3. Called by aws.py during full corpus deploys. |
upload_blog_corpus.py |
Builds the thingelstad.com blog corpus from data/blog/posts, embeds via Bedrock Cohere with S3 cache reuse, uploads to S3. Called by aws.py during full corpus deploys. |
upload_podcast_corpus.py |
Builds the Another Thing podcast corpus from data/podcast/another-thing/episodes, embeds via Bedrock Cohere with S3 cache reuse, uploads to S3. Called by aws.py during full corpus deploys. |
bedrock_logging.py |
Configures Bedrock model invocation logging (CloudWatch destination + S3 archive). One-time setup. |
Invocation
Normal deployments run in GitHub Actions after a verified commit/push.
make librarian-deploy queues a code-only deploy of remote main and prints its
run URL; it needs GitHub authentication but no local AWS CLI session. Wait for
the run before reporting acceptance. Direct
uv run --locked python pipeline/deploy/aws.py is an exceptional local operation
requiring valid AWS credentials.
Local .env selects AWS_PROFILE=cloud-engineer; keep AWS access-key variables out of
that file and the calling shell so they cannot override the profile. Verify
local access with aws sts get-caller-identity --profile cloud-engineer. This session is
needed only for local AWS operations, not the GitHub deployment shortcut.
The deploy script must run through the locked uv environment so dependencies such as
boto3 and python-dotenv are available; do not invoke it with bare system Python.
# Default for code changes — skip the slow + paid corpus reupload
make librarian-deploy ARGS="--skip-corpus-upload"
# Full deploy (re-embeds + uploads all three corpora)
make librarian-deploy-full
# Exceptional local deployment when bypassing make
uv run --locked python pipeline/deploy/aws.py --skip-corpus-upload
The --skip-corpus-upload flag is the default for any code-only change (Lambda code, CloudFormation tweaks, env-var changes). Full corpus reupload refreshes Weekly Thing + blog + podcast and is slow/paid. Each source-specific uploader reuses embeddings from the previously deployed S3 artifact when chunk ids match. Only do a full deploy when:
- The corpus itself is stale (new issues to embed)
- The embed model has changed (Cohere v3 → v4, hypothetically)
- The corpus schema has changed (e.g., new chunk metadata field)
CI in .github/workflows/deploy.yml uploads all three corpus artifacts when production runs. External content first enters Studio through .github/workflows/sync-external-content.yml, which commits changes under data/blog/** and data/podcast/**; those commits then trigger production. Manual deploys are for local validation before commit.
aws.py flow
- Smoke-test the Thingy model buckets via minimal
InvokeModelcalls againstTHINGY_DEFAULT_MODEL,THINGY_FAST_MODEL, andTHINGY_ADVANCED_MODEL. Refuses to deploy if any configured model isn't accessible from this account. Pass--skip-smoke-testto override. - Verify private bucket security (
verify_private_bucket(bucket)). Administrator-only--bootstrap-bucketcreates or hardens it. Default bucket:LIBRARIAN_BUCKETenv var orweekly-thing-librarian. - Package both Lambda bundles (
auth/+chat/) — separate npm install + zip per bundle. Bundles ship independently because the auth Lambda is REST and the chat Lambda is response-streamed Function URL. - Upload zips to
s3://{bucket}/code/{auth,chat}-lambda/<unix-ts>.zip. Timestamp keys so CloudFormation always sees a new version. - Optional: all corpus uploaders run — Weekly Thing corpus + graph, blog corpus, podcast corpus.
- CloudFormation update-stack with the new code keys + secrets from
.env:SESSION_SECRET,LIBRARIAN_RETRIEVE_SECRET,BUTTONDOWN_API_KEY. - 30-day log retention on the auto-created log groups (
configure_log_retention). - Update
.envwith the latest stack outputs:LIBRARIAN_API_URL,LIBRARIAN_STREAM_URL.
Corpus upload flow
- Weekly Thing:
upload_corpus.pybuilds fromdata/issues/*/archive.md, embeds chunks, builds the graph, uploadscorpus.json+graph.json. - Blog:
upload_blog_corpus.pybuilds fromdata/blog/posts/**/*.md, reuses cached embeddings by chunk id, uploadsblog_corpus.json. - Podcast:
upload_podcast_corpus.pybuilds fromdata/podcast/another-thing/episodes/*.json, reuses cached embeddings by chunk id, uploadspodcast_corpus.json. - The Lambda's
loadCorpus(),loadBlogCorpus(), andloadPodcastCorpus()pick up new files on the next cold start or afteraws.pytriggers a new deployment.
Use make librarian-corpora-upload when code is unchanged and only the three S3 corpus artifacts need refresh.
Secrets
Pulled from the repo-root .env. Required:
| Var | Source | What for |
|---|---|---|
BUTTONDOWN_API_KEY |
account secrets | Auth Lambda's subscriber verification |
LIBRARIAN_SESSION_SECRET |
(auto-generated if missing) | HMAC signing for session JWTs |
LIBRARIAN_RETRIEVE_SECRET |
shared secret | trusted /retrieve service auth |
| AWS deployment credentials | GitHub OIDC | Temporary per-run credentials; no local AWS session needed |
The CloudFormation stack uses the scoped weekly-thing-librarian-cloudformation service role. CI also needs scoped corpus reads, Bedrock invocation, bucket-security inspection, and log setup. See iam/README.md for source policies and legacy local-consumer retirement.
Bedrock regions
- Embed model (
cohere.embed-english-v3): us-east-1. - Rerank model (
cohere.rerank-v3-5:0): us-west-2 — only region with the rerank model. The Lambda'sBedrockAgentRuntimeClientis constructed with explicitregion: 'us-west-2'override. - Default model (
us.anthropic.claude-sonnet-4-6): cross-region inference profile for main chat/persona work. - Fast model (
us.anthropic.claude-haiku-4-5-20251001-v1:0): cross-region inference profile for small structured/background work. - Advanced model (
us.anthropic.claude-opus-4-6-v1): cross-region inference profile for high-synthesis work.
Don't move the rerank region. Don't change model bucket assignments without smoke-testing them against the deploy's account.
CloudFormation template
Lives at apps/librarian/infra/cloudformation.yaml. Three Lambdas (auth + stream + eval), API Gateway (REST), Lambda Function URL (Stream, RESPONSE_STREAM mode), DynamoDB (canonical conversations + rate limits + user memory, with Streams enabled for eval), IAM role + policies, CloudWatch log groups, Bedrock IAM policies for embed/rerank/invoke, and a DynamoDB Stream event source mapping for the eval Lambda. See ../../apps/librarian/AGENTS.md for the runtime side of what's deployed.
Conventions
--skip-corpus-uploadis the default for code changes. (Memory:reference_librarian_deploy_flags.md.)- Don't disable the smoke test casually. The Bedrock model access check at deploy time prevents the most common "deployed but immediately broken" failure mode.
- Stack name is
weekly-thing-librarian(STACK_NAMEinaws.py). Don't rename without coordinating with.env-referenced outputs. - Log retention is 30 days. Bedrock invocation logs go to a separate longer-retention destination via
bedrock_logging.py.
