Strands agent with 10 tools built on AgentCore Runtime; JWT userId extraction added

dropthejase built the AI chat core as a TypeScript Strands agent running on AWS AgentCore Runtime - replacing upstream's inline LLM loop with a proper tool-dispatching agent that handles document reads, edits, generation, workflow access, and per-user credits tracking. The tool definitions are largely reusable independent of Strands.

draftinginfrastructure

The agents/ workspace scaffolds a Strands SDK agent with 10 tools across three groups. Document read tools: read_document, find_in_document, list_documents, fetch_documents. Document write tools: generate_docx (structured section/table schema), edit_document (tracked-change edits via applyTrackedEdits), replicate_document. Metadata tools: list_workflows, read_workflow, plus a read_table_cells that is later removed because review_id can't be supplied to the main agent.

Shared libs cover secrets (Secrets Manager cold-start cache), storage (S3 get/put/presign), and a citations.ts parser. Doc context is built per-turn: buildDocContext for standalone documents, buildProjectDocContext for project-scoped sessions. The agent factory instantiates a BedrockModel with cross-region inference profile IDs (e.g. eu.anthropic.claude-sonnet-4-6-20250922-v1:0 for eu-west-1) and assembles tools from the factory functions.

A later commit (7024448) stops trusting userId from the request body and instead extracts it from the Bearer JWT sub claim at the HTTP layer before any agent code runs. The same commit removes the backend's chat_messages write - persistence ownership moves to the agent side.

Subsequent iterations migrate from Supabase to Aurora RDS Data API, add a AfterModelCallEvent hook that increments per-user DynamoDB credits, and create a dedicated AgentCore execution IAM role with CloudWatch Logs, X-Ray, and scoped Bedrock permissions.

The tool factory pattern (each tool returned by a make*Tool(deps) function rather than defined at module scope) is what makes the tools testable in isolation. The citations.ts lib is also clean enough to reuse without the rest of the agent.

So what Worth a look if you are considering AgentCore or Strands for your agent layer. The individual tool implementations - especially `read_document`, `find_in_document`, and `generate_docx` - are portable; the agent factory and AgentCore execution role are integration-specific. The JWT extraction pattern (`extractUserIdFromBearer`) is a required safety step if you replicate this architecture.

View this fork on GitHub →

Spotted something wrong? Or know the PR text has fresher detail than the writeup above?

Commits in this thread

18 commits from dropthejase/louis, oldest first. Source extracted verbatim from the harvested git log.

SHA Subject Author Date
251e9c03 feat(agent): scaffold agent project Jason Lee 2026-05-06 ↗ GitHub
a5c9b69d feat(agent): add shared libs (secrets, supabase, storage, citations) Jason Lee 2026-05-06 ↗ GitHub
e1cbe922 feat(agent): add doc context builders Jason Lee 2026-05-06 ↗ GitHub
1991d944 feat(agent): add read_document, find_in_document, list_documents, fetch_documents tools Jason Lee 2026-05-06 ↗ GitHub
3cc52512 feat(agent): add generate_docx, edit_document, replicate_document tools Jason Lee 2026-05-06 ↗ GitHub
2c3be9bc feat(agent): add read_table_cells, list_workflows, read_workflow tools Jason Lee 2026-05-06 ↗ GitHub
6599ac1a feat(agent): add agent factory and Express entrypoint Jason Lee 2026-05-06 ↗ GitHub
4e162dd3 feat(agent): add Dockerfile and AgentCore config Jason Lee 2026-05-06 ↗ GitHub
80b71215 feat(agent): add deploy and invoke scripts Jason Lee 2026-05-06 ↗ GitHub
34dd571c Merge feature/agent: AgentCore Strands agent with 10 tools Jason Lee 2026-05-06 ↗ GitHub
7024448a refactor(agent): extract userId from JWT, remove chat_messages write, remove unused body fields Jason Lee 2026-05-07 ↗ GitHub
Extracts userId from Bearer JWT (sub claim) instead of trusting the
POST body, returns 401 if missing. Removes the assistant chat_messages
insert (frontend will own that write). Drops userId and messages from
the body type.
3d61162e feat(agent): wire real tracked-change edits in edit_document tool Jason Lee 2026-05-07 ↗ GitHub
commit body
Copies docxTrackedChanges.ts from backend into agent/src/lib/docx-tracked-changes.ts.
edit-document.ts now calls applyTrackedEdits() to produce real w:ins/w:del
markup instead of uploading the unchanged buffer. Adds fast-xml-parser
dependency required by the tracked-changes implementation.
d82c2674 chore(agent): update package-lock after npm install Jason Lee 2026-05-07 ↗ GitHub
9f58de0a feat(agent): create AgentCore execution role with CloudWatch Logs, X-Ray, and Bedrock permissions Jason Lee 2026-05-08 ↗ GitHub
d4323754 Migrate agent from Supabase to Aurora RDS Data API, add JWT userId extraction, add credits hook Jason Lee 2026-05-08 ↗ GitHub
a8c168ec Fix stale Supabase reference in agentcore.ts comment Jason Lee 2026-05-09 ↗ GitHub
cdc65caf refactor(main-agent): remove dead read_table_cells tool Jason Lee 2026-05-14 ↗ GitHub
No way to supply review_id to the main agent - tool was never callable
in practice. Tabular chat uses the dedicated tabular agent instead.
78cfd4e0 chore(main-agent): remove diagnostic STS identity logging and unused client-sts dependency Jason Lee 2026-05-14 ↗ GitHub

Capture this thread into my fork

Download a single Markdown prompt that tells Claude how to port every commit above into your working tree — adapting paths and structure to match your repo. Run it via claude -p < capture-thread-333.md from inside the repo you want the changes in.

⬇ Download capture-thread-333.md