Skip to content

the web chat runs locally - #71

Merged
mxfactorial merged 3 commits into
mainfrom
local-chat
Oct 22, 2026
Merged

mxfactorial merged 3 commits into
mainfrom
local-chat

Conversation

@mxfactorial

Copy link
Copy Markdown
Contributor

A person signs in at :3000, opens a gerp, and its chat link reaches the agent on an Anthropic key. No AWS credentials, no docker. Closes #70.

what changed

  • the agent is a process, :8090 — the container's entrypoint.py under uvicorn, started by scripts/local-dev.sh. It finds dev/ beside itself with no /repo mount, makes its Anthropic client on the first turn (the stack starts without a key; a turn then answers with the error), and answers POST /runtimes/{arn}/invocations, the path the SDK sends InvokeAgentRuntime to, beside /invocations. It also hands the Anthropic API the system blocks in that API's shape — the cache point becomes cache_control. That was a live 400 on the container smoke path too.
  • the Node lambdas run locally — tests/server/node_lambda.mjs is _image.py for an index.mjs: the snapshot's env applied with the shell winning, the handler imported from source and re-imported on edit, awslambda shimmed for a streaming handler. One process per Function URL: the web chat on :8082, the storage portal on :8083. POST /invoke/<fn> runs a Node tool; dev/tools.py and the image's /fn/<name> mount call it.
  • the SDK is pointed by env, no shared module: AWS_ENDPOINT_URL at moto, AWS_ENDPOINT_URL_BEDROCK_AGENTCORE at the agent process. No handler carries a local branch.
  • every local token is signed — the Cognito stand-in makes an RS256 key at start, lists it at /.well-known/jwks.json, and issues tokens at GET /token?sub=&email=. The chat lambda reads COGNITO_ISSUER over the pool's derived issuer (one config read); the bff image verifies every bearer against the stand-in; /dev/login and the e2e login helper take their tokens from it. An unsigned token gets in nowhere.
  • LOCAL_CHAT_URL on the gerp row the bff seeds, so the owner app's chat link opens the local door with the token in the fragment, as in production.
  • modules/agent/dev/run.sh and main() in dev/agent.py are deleted. run_turn stays for the engine, the smoke and the flow test.

the image

entrypoint.py is the one file in the agent image that changes. StrandsEngine, the production engine, is untouched; the new route is never hit by AgentCore. It rides the next deploy.sh image.

tests

  • tests/server/local/test_local_tokens.py — sign/verify, every refusal, the stand-in's tokens, the bff image's claims
  • tests/server/local/test_node_lambda.py — env, the shell winning, the Function URL event streamed through, live edit, the image's forward
  • tests/agent/local/test_local_chat.py — the chat lambda against the stand-in and a stub agent over moto: the issuer honored, the invoke on the SDK's path with the session header and the payload, the buffered reply as one chunk, the chats row
  • tests/agent/local/test_local_agent_process.py — the route, the system blocks, the engine starting without a key

Browser smoke, local: 23 passed, 4 skipped as before. Full suite: 1765 passed across 208 files. One real turn through the door by hand: "local chat works".

A person signs in at :3000, opens a gerp, and its chat link reaches the
agent — on an Anthropic key, no AWS credentials, no docker. Closes #70.

- the container entrypoint runs as a process on :8090 under local-dev.sh:
  it finds dev/ beside itself with no /repo mount, makes its Anthropic
  client on the first turn so the stack starts without a key, answers
  POST /runtimes/{arn}/invocations (the path the SDK sends
  InvokeAgentRuntime to) beside /invocations, and hands the Anthropic
  API the system blocks in that API's shape — a cache point becomes
  cache_control. That last one was a live 400 on the container smoke path
- tests/server/node_lambda.mjs runs a Node lambda the way _image.py runs
  a Python one: the snapshot's env applied (the shell winning), the
  handler imported from source and re-imported on edit, awslambda shimmed
  for a streaming handler. One process per Function URL — the chat on
  :8082, the storage portal on :8083 — and POST /invoke/<fn> for a Node
  tool, which dev/tools.py and the image's /fn/<name> mount call
- the Node lambdas' SDK is pointed by env, no module: AWS_ENDPOINT_URL at
  moto, AWS_ENDPOINT_URL_BEDROCK_AGENTCORE at the agent process
- the Cognito stand-in signs RS256 with a key made at start, lists it at
  /.well-known/jwks.json and issues tokens at GET /token?sub=&email=. The
  chat lambda reads COGNITO_ISSUER over the pool's derived issuer; the
  bff image verifies every bearer against the stand-in; /dev/login and
  the e2e login helper take their tokens from it. An unsigned token gets
  in nowhere
- LOCAL_CHAT_URL on the gerp row the bff seeds, so the owner app's chat
  link opens the local door with the token in the fragment
- modules/agent/dev/run.sh and main() in dev/agent.py are deleted:
  run_turn stays for the engine, the smoke and the flow test

Tests: tests/server/local/test_local_tokens.py, test_node_lambda.py,
tests/agent/local/test_local_chat.py (the chat lambda against the
stand-in and a stub agent, over moto), test_local_agent_process.py.
Browser smoke local: 23 passed. Full suite: 1765 passed.

Co-Authored-By: Claude <noreply@anthropic.com>
Comment thread tests/server/node_lambda.mjs Fixed
mxfactorial and others added 2 commits October 21, 2026 21:16
CodeQL on #71: console.error read a template holding the request url as
its format string (js/tainted-format-string); the url is now a %s
argument. The image's 502 for a Node process that did not answer carried
the exception's text (py/stack-trace-exposure); it names the address, and
the exception goes to the process log.

Co-Authored-By: Claude <noreply@anthropic.com>
CI on #71: test_dev_login called the verb in-process and the token now
comes from the Cognito stand-in over HTTP, which the unit job does not
run. The test starts the stand-in (tests/server/cognito/standin.py, the
starter the chat contract test uses too) and verifies the token against
its jwks. Without the stand-in the verb answers 503 naming its address
and the start command instead of a traceback, and a test says so.

Co-Authored-By: Claude <noreply@anthropic.com>
@mxfactorial
mxfactorial merged commit 0c4acad into main Oct 22, 2026
8 checks passed
@mxfactorial
mxfactorial deleted the local-chat branch October 22, 2026 04:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

the chat door, locally — the agent on an Anthropic key, no AWS, no docker

2 participants