External API reference
August 21, 2026 · View on GitHub
Notes on the third-party API shapes this code is written against, gathered from the LangChain and AWS docs (pinned 2026-07-01). These are the details that are easy to get wrong or that changed recently, kept here so the code and the docs don't drift. Sources are at the end of each section.
1. deepagents (LangChain Deep Agents)
- Install
deepagents(stable 0.7.x — the repo pins>=0.7.0,<0.8), Python ≥ 3.11. Extras:deepagents[aws]for Bedrock,deepagents[quickjs]for dynamic subagents. - 0.7.0 breaking changes this repo codes against:
TodoListMiddlewareis NO LONGER a default — without it there is nowrite_todostool,todosstate, or planning prompt. The harvest attaches it explicitly to the MAIN agent (from langchain.agents.middleware import TodoListMiddleware); sub-agents stay lean (their prompts don't plan with todos).- Default prompts are lean: the authored base prompt is empty and the
tool-usage prose constants (
TASK_SYSTEM_PROMPT,FILESYSTEM_SYSTEM_PROMPT, …) are gone. Fine here — every role's prompt is fully authored inprompts.py. - A recursive
deletefilesystem tool is exposed to the model whenever the backend supports it (FilesystemBackend does). The OKF guard refuses it unconditionally — nothing in a bundle is agent-deletable. write_filenow OVERWRITES an existing file instead of returning a file-exists error (no create-only mode). The guard engine already validates overwrites against the existing doc (augmentation rules), so this is covered.BackendProtocol.read()returns aReadResult(error/file_data{"content"}/ line-range fields), not the rendered string — the benchmark solver's customread_filerenders it viaformat_content_with_line_numbers(seesolver._read_text).FilesystemBackend/LocalShellBackenddefaultvirtual_mode=Truenow (we always passed it explicitly).read_fileoutput uses dynamic-width line numbers + two-space separator (no fixedcat -ngutter); agent-facing emptyls/globrender asNo files found.
from deepagents import create_deep_agent, CompiledSubAgent- Signature (everything after
toolsis keyword-only):
Returns a compiled LangGraph graph; callcreate_deep_agent( model: str | BaseChatModel | None = None, tools=None, *, system_prompt: str | SystemMessage | None = None, # not "instructions" middleware: Sequence[AgentMiddleware] = (), subagents=None, backend: BackendProtocol | None = None, **model_kwargs, # e.g. model_provider="bedrock_converse", temperature=... ).invoke({"messages": [...]}),.ainvoke(...), or.stream(...). - Bedrock model. Build a
ChatBedrockConverseand pass it ascreate_deep_agent(model=chat_model, ...). It comes from the standalonelangchain-awspackage (pip install langchain-aws), not thelangchain[aws]extra. Building the model explicitly lets adaptive-thinking config ride on it viaadditional_model_request_fields(see §7 ofOKF_DESIGN.md). - Backends (
from deepagents.backends import ...):StateBackend(default, ephemeral in graph state),FilesystemBackend(root_dir=<abs>, virtual_mode=True)(real files —virtual_mode=Trueis required, the defaultFalsegives no path confinement), andCompositeBackend(default=..., routes={"/prefix/": ...}). The recommended setup isCompositeBackend(default=StateBackend(), routes={"/workspace/": FilesystemBackend(root_dir=..., virtual_mode=True)}), so the agent's internal files (/large_tool_results/,/conversation_history/) stay ephemeral and only/workspace/hits disk. - Built-in tools:
ls,read_file,write_file,edit_file,delete,glob,grep,task— pluswrite_todoswhenTodoListMiddlewareis attached (no longer a default in 0.7).write_file(file_path, content)creates OR fully replaces;edit_file(file_path, old_string, new_string)is an exact-string replace (Claude Code semantics);delete(file_path)is recursive (refused by the OKF guard in this repo);read_filesupports pagination. - Subagent dict (
SubAgent):name(required),description(required),system_prompt(required — never inherited),tools(optional — when set it replaces the inherited tools),model(optional — inherits the parent),middleware(optional — not inherited; appended to the subagent's default stack). Exposed to the parent through thetasktool. - Dynamic fan-out: add
CodeInterpreterMiddlewarefromlangchain_quickjs(needsdeepagents[quickjs]); with subagents configured, agent code can call atask()global to fan out. Beta. - Middleware:
from langchain.agents.middleware import AgentMiddleware.def wrap_tool_call(self, request, handler): name = request.tool_call["name"] # dict indexing, not request.name args = request.tool_call["args"] # short-circuit (the tool never runs): return a ToolMessage/Command without handler return ToolMessage(content="refused", tool_call_id=request.tool_call["id"]) # or override args then run: return handler(request.override(...)) # or run normally: return handler(request)ToolMessageis fromlangchain.messages,Commandfromlanggraph.types. Custommiddleware=is appended to the main stack. The guard has to be attached to each subagent'smiddlewarelist too, since subagent middleware doesn't inherit.
2. Bedrock AgentCore Runtime
- SDK:
bedrock-agentcore. Container images must be ARM64. - HTTP agent (harvest):
Long work goes on a background thread reportingfrom bedrock_agentcore.runtime import BedrockAgentCoreApp app = BedrockAgentCoreApp() @app.entrypoint def invoke(payload, context=None): ... # payload = deserialized body @app.ping def ping(): return "HealthyBusy" if busy else "Healthy" app.run() # serves /invocations + /ping on 0.0.0.0:8080HealthyBusy, so the session isn't idled out (8h cap). Don't advancetime_of_last_updateon every ping. - MCP server (consumption): serve on
0.0.0.0:8000/mcp, stateless streamable-HTTP.
It has to accept the platform-injectedmcp = FastMCP(host="0.0.0.0", stateless_http=True) mcp.run(transport="streamable-http") # port 8000, path /mcpMcp-Session-Idheader. - Deploy (control plane):
boto3.client("bedrock-agentcore-control") .create_agent_runtime(agentRuntimeName, agentRuntimeArtifact={"containerConfiguration": {"containerUri": "<ecr>:<tag>"}}, networkConfiguration={"networkMode": "VPC", "networkModeConfig": {"subnets": [...], "securityGroups": [...]}}, roleArn, protocolConfiguration={"serverProtocol": "HTTP"|"MCP"}, authorizerConfiguration, filesystemConfigurations=[...]). - Invoke (data plane):
boto3.client("bedrock-agentcore") .invoke_agent_runtime(agentRuntimeArn=..., payload=<bytes>, runtimeSessionId=..., qualifier="DEFAULT").runtimeSessionIdis the dataset id, giving session affinity. - Filesystem config:
{"s3FilesAccessPoint": {"accessPointArn": "arn:aws:s3files:<region>:<acct>:file-system/<fs>/access-point/<ap>", "mountPath": "/mnt/data"}}. Runtime-scoped, shared across all sessions, VPC required, mountPath under/mnt. The exec role needss3files:ClientMount/ClientWrite/GetAccessPoint. - JWT auth:
authorizerConfiguration={"customJWTAuthorizer": {"discoveryUrl": "<issuer>/.well-known/openid-configuration", "allowedClients": [<client_id>]}}. The discoveryUrl has to end with/.well-known/openid-configuration. - Terraform (
hashicorp/aws ~> 6.0):aws_bedrockagentcore_agent_runtimewithagent_runtime_artifact { container_configuration { container_uri } },network_configuration { network_mode network_mode_config { subnets security_groups } },protocol_configuration { server_protocol = "HTTP"|"MCP" }(a block, not a string),authorizer_configuration { custom_jwt_authorizer { discovery_url allowed_clients } },filesystem_configuration { s3_files_access_point { access_point_arn mount_path } }, andlifecycle_configuration { idle_runtime_session_timeout max_lifetime }. The nested configs are HCL blocks;environment_variablesandtagsare maps.
2b. AgentCore Gateway + the web-search connector (chat web_search)
A Gateway is an MCP front end for tools AgentCore hosts. Web Search is
reachable only this way — there is no direct search API — so the chat agent
gets it by talking MCP to a gateway whose single target is the built-in
web-search connector.
Not to be confused with Bedrock's own "Web Search" tool (Aug 2026): a server-side built-in tool over the same Amazon index, offered only on the
bedrock-mantleResponses API (GPT-5.x — not Converse/Anthropic, notbedrock-runtime), with Search + Fetch (cached page content) ops,url_citationannotations, its own IAM namespace (bedrock-websearch:InvokeSearch/InvokeFetch), and anexternal_web_accessparameter — defaulttrueto match OpenAI, but live-web retrieval is additionally gated bybedrock-websearch:ExternalWebAccess, whichAmazonBedrockFullAccessdeliberately omits because external fetch is a data-exfiltration channel for an agent holding internal data (encode data in a URL, fetch it). The chat agent deliberately does NOT use it: the gateway connector serves every chat model (Anthropic included), is search-only, and stays in-AWS; the chat role holds nobedrock-websearch:*actions, and our Mantle requests never send the built-in tool (our LangChainweb_searchis atype: "function"tool — the shared name does not trigger thetype: "web_search"built-in).
- Gateway (Terraform, native):
aws_bedrockagentcore_gatewaywithname role_arn protocol_type = "MCP"andauthorizer_type = "AWS_IAM"(valid:CUSTOM_JWT,AWS_IAM;authorizer_configuration { custom_jwt_authorizer { discovery_url … } }is required only forCUSTOM_JWT). Exportsgateway_id,gateway_arn, andgateway_url— the…/mcpendpoint. - Connector target — NOT natively supported.
aws_bedrockagentcore_gateway_target'starget_configuration.mcpacceptslambda/api_gateway/mcp_server/open_api_schema/smithy_modelbut noconnectorblock (verified against provider 6.54/6.56).AWS::BedrockAgentCore::GatewayTargetin the CloudFormation registry does supportMcp.Connectorand isFULLY_MUTABLE, so we declare the target withaws_cloudcontrolapi_resource(same provider, still declarative):
Connector targets need nodesired_state = jsonencode({ Name = "<target>", GatewayIdentifier = "<gateway_id>" TargetConfiguration = { Mcp = { Connector = { Source = { ConnectorId = "web-search" } Configurations = [{ Name = "WebSearch", ParameterValues = {} }] } } } CredentialProviderConfigurations = [{ CredentialProviderType = "GATEWAY_IAM_ROLE" }] })iamCredentialProvider(the connector's service is known to the gateway); MCP-server and OpenAPI targets do. - Connector versioning — the CFN registry can't pin. The control-plane API
accepts
source: {connectorId, version}, andversion: "1.2.0"is what turns on the request-levelfilters(below) plus the target-level domain include list — a gateway snapshots the tool schema at target creation, and an unpinned target keeps the pre-1.2.0 schema and silently ignores afiltersargument (verified live, Aug 2026). ButAWS::BedrockAgentCore::GatewayTarget'sConnectorSourcecarries onlyConnectorId(additionalProperties: falsein the live registry schema, all three connector regions), so neither Cloud Control norawscccan express the pin. The shipped CLI/SDK models lag too: as of aws-cli 2.35.8 / botocore 1.43.44 theirConnectorSourcealso has onlyconnectorId, soupdate-gateway-targetwithsource.versionfails CLIENT-SIDE (ParamValidation) while the service accepts it (raw call →202, schema gainsfilters— verified live). Our bridge: aterraform_datastep sendsUpdateGatewayTargetraw —PUT /gateways/{id}/targets/{targetId}/onbedrock-agentcore-control.<region>, SigV4 service namebedrock-agentcore, signed by the stdlib-python scriptinfra/compute/files/web_search_pin.pyfed byaws configure export-credentials(curl's--aws-sigv4is not portable: macOS's SecureTransport curl silently downgrades it to Basic auth). The step is the single writer forparameterValues(both domain lists) — seeinfra/compute/web_search.tf. Fold it back into the CLI, then the declarative resource, as the models and the CFN registry catch up. - Operator domain filtering (
parameterValues.domainFilter):exclude(any connector version) andinclude(1.2.0+), ≤100 bare domains each, subdomains match, enforced server-side and invisible to the model. Request-level lists compose with them — target-level constraints can never be relaxed per call. - IAM, two roles. The gateway service role (trust
bedrock-agentcore.amazonaws.com) needsbedrock-agentcore:InvokeGatewayon…:gateway/*andbedrock-agentcore:InvokeWebSearchon the service-owned ARNarn:aws:bedrock-agentcore:<region>:aws:tool/web-search.v1(account field is literallyaws; region is the gateway's). The caller (our chat runtime) needs onlybedrock-agentcore:InvokeGatewayon the gateway ARN. - Invoke (MCP over HTTPS, SigV4 service
bedrock-agentcore): POST JSON-RPC togateway_url.initialize(protocolVersion"2025-06-18") returns anMcp-Session-Idheader to echo on subsequent calls; thentools/call {name, arguments}. SendAccept: application/json, text/event-stream— the reply may be either plain JSON or SSEdata:frames. - Tool naming:
${target_name}___${tool_name}(three underscores), e.g.okf-web-search___WebSearch. WebSearchI/O: input is{query (≤200 chars, required), maxResults (1–25, default 10), filters?}.filters(connector 1.2.0+ only — absent from the schema, and silently ignored on the wire, on older targets) is{domainFilter: {include?, exclude?}, publishedDateFilter: {from?, to?}}— ≤100 bare domains per list (root domain matches subdomains), inclusive ISO-8601 UTC date bounds matching each page's publication date. The result is an MCP tool result whose text block is JSON:{id, results:[{text, url, title, publishedDate}]}(url/title/publishedDateall optional;publishedDateformat is unpinned — ISO dates but also prose like"05:00PM, Sunday, October 06 2024, PDT"observed live). Our wrapper surfaces the filters aspublished_after/published_before/include_domains/exclude_domains, gated onOKF_WEB_SEARCH_FILTERS_ENABLED(services/chat/src/chat/web_search.py).- Region + acceptable use: connector available in
us-east-1,eu-west-1, andap-northeast-1. Queries are served inside AWS (not handed to a third-party engine), but AWS's terms require retaining the source citations/links in anything shown to end users, and forbid bulk extraction or building a competing index.
3. S3 Vectors — boto3.client("s3vectors")
create_vector_bucket(vectorBucketName=...).create_index(vectorBucketName, indexName, dataType="float32", dimension=512, distanceMetric="cosine", metadataConfiguration={"nonFilterableMetadataKeys": ["title","description","s3_key"]}). Thedimension,distanceMetric,dataType, andmetadataConfigurationare immutable — changing one means replacing the index and re-embedding.put_vectors(vectorBucketName, indexName, vectors=[{"key": <path>, "data": {"float32": [floats, len == dim]}, "metadata": {...}}]), up to 500 per call. Notedatais a tagged union{"float32": [...]}, not a flatvectorkey. An existing key is fully overwritten.query_vectors(vectorBucketName, indexName, topK, queryVector={"float32":[...]}, filter={...}, returnMetadata=True, returnDistance=True)returns{"vectors": [{"key","distance","metadata"}], "distanceMetric", "nextToken"}. Page ≤ 100.delete_vectors(vectorBucketName, indexName, keys=[...]), up to 500.- Filter operators:
$eq $ne $gt $gte $lt $lte $in $nin $exists $and $or(no prefix, substring, or regex). Range operators are number-only.$eqagainst a list value matches any element. - The 403 trap:
query_vectorswith a filter orreturnMetadata=Truealso needss3vectors:GetVectors, not justs3vectors:QueryVectors. - Filterable metadata is ≤ 2 KB per vector (≤ 40 KB total); non-filterable keys are ≤ 10 per index.
- Terraform (
hashicorp/aws ~> 6.0):aws_s3vectors_vector_bucketandaws_s3vectors_index(argsdata_type,dimension,distance_metric, and ametadata_configuration { non_filterable_metadata_keys = [...] }block — all force replacement, matching the API's immutability).
4. Titan V2 / Glue / Athena / EventBridge
- Titan V2:
boto3.client("bedrock-runtime").invoke_model(modelId= "amazon.titan-embed-text-v2:0", body=json.dumps({"inputText": t[:50000], "dimensions": 512, "normalize": True})).dimensionsis only 1024, 512, or 256. Read the result atjson.loads(resp["body"].read())["embedding"]. Throttling raisesThrottlingException; retry with backoff. - Glue (
boto3.client("glue")):get_databases/get_tablespage at 100,get_partitionsat 1000; alsoget_table_versions.Table.StorageDescriptor.Columns[]is{Name, Type (Hive string), Comment};Table.PartitionKeys[]has the same shape. Detect change viaTable.UpdateTimeandVersionId(monotonic), neverLastAccessTime. - Athena (
boto3.client("athena")):start_query_execution(QueryString, QueryExecutionContext={"Database": ...}, ResultConfiguration={"OutputLocation": "s3://..."} | WorkGroup=...), then pollget_query_execution(...)["QueryExecution"]["Status"]["State"]until a terminal state (SUCCEEDED,FAILED,CANCELLED— two Ls), thenget_query_results(...). Row 0 is the header; cells are atRows[].Data[].VarCharValue; paginate withNextToken. - Glue change event:
{"source": ["aws.glue"], "detail-type": ["Glue Data Catalog Table State Change"]}; detail{databaseName, tableName, typeOfChange, changedPartitions}. - S3 events: enable with
aws_s3_bucket_notification { eventbridge = true }(all events flow to the default bus; filter in the rule). Event{"source": ["aws.s3"], "detail-type": ["Object Created","Object Deleted"]}; detail{bucket.name, object.key, object.size, object.sequencer}. Order and dedup onobject.sequencer— a hex string, comparable lexicographically per key.
5. Terraform (hashicorp/aws ~> 6.0, native throughout)
aws_cognito_user_poolexposesendpoint=cognito-idp.<region>.amazonaws.com/<poolId>(no scheme). Issuer ishttps://${endpoint}; discovery is${issuer}/.well-known/openid-configuration.aws_cognito_user_pool_client:allowed_oauth_flows_user_pool_client=true,allowed_oauth_flows=["code"],allowed_oauth_scopes,callback_urls,logout_urls,supported_identity_providers=["COGNITO"]. A SPA client has no secret.- API Gateway v2:
aws_apigatewayv2_api(protocol_type="HTTP") +aws_apigatewayv2_authorizer(authorizer_type="JWT",identity_sources=["$request.header.Authorization"],jwt_configuration { audience=[client_id], issuer="https://${endpoint}" }) +aws_apigatewayv2_integration(AWS_PROXY,integration_uri=<lambda invoke_arn>,payload_format_version="2.0") +aws_apigatewayv2_route(route_key,authorization_type="JWT",authorizer_id) +aws_apigatewayv2_stage(name="$default",auto_deploy=true).aws_lambda_permissionusessource_arn = "${aws_apigatewayv2_api.x.execution_arn}/*/*". aws_lambda_function(package_type Image or Zip),aws_iam_role+aws_iam_role_policy(managed_policy_arns/inline_policyare deprecated),aws_lambda_event_source_mapping(SQS:event_source_arn,batch_size,function_response_types=["ReportBatchItemFailures"], nostarting_position).aws_dynamodb_table(PAY_PER_REQUEST; declareattributeblocks only for key attributes, or you get a perpetual diff).aws_s3_bucket_notification { eventbridge = true }(atomic — one per bucket) +aws_cloudwatch_event_rule(event_patternviajsonencode) +aws_cloudwatch_event_target+aws_sqs_queue+aws_sqs_queue_policy(Principalevents.amazonaws.com, must includeVersion="2012-10-17").aws_cloudfront_origin_access_control(signing_protocol="sigv4") +aws_cloudfront_distribution(OAC origin). The SPA fallback is acustom_error_responsefor both 403 and 404 → 200/index.html, since OAC on S3 returns 403 for a missing object.- EventBridge S3 key filter: an array of content filters under one field is OR,
not AND —
object.key = [{prefix},{suffix}]matches prefix OR suffix. For prefix-AND-suffix, use a single wildcard:object = { key = [{ wildcard = "okf/*.md" }] }. - S3 Files (native):
aws_s3files_file_system(bucket= bundle-bucket ARN,role_arn,accept_bucket_warning, optionalprefix; the role is assumed byelasticfilesystem.amazonaws.com) +aws_s3files_mount_target(file_system_id,subnet_id,security_groups) +aws_s3files_access_point(file_system_id,root_directory { path = "/okf" },posix_user { uid gid }; exportsarn). Mount thatarnin the runtime'sfilesystem_configuration. - Backend:
terraform { backend "s3" { bucket, key, region, use_lockfile = true } }(DynamoDB locking is deprecated). Cross-stack reads usedata "terraform_remote_state"(root-level outputs only). - Everything, including S3 Vectors, AgentCore, and S3 Files, is native in
hashicorp/aws ~> 6.0(v6.45+); the awscc provider isn't needed. - The registry docs render client-side (empty to WebFetch), so read them from
raw.githubusercontent.com/hashicorp/terraform-provider-aws/main/website/docs/r/<name>.html.markdown. Newer services likes3filesmay not have published docs yet — read the resource's Go schema underinternal/service/<svc>/.
6. React + Cognito OIDC (react-oidc-context on oidc-client-ts)
AuthProviderconfig:authority="https://cognito-idp.<region>.amazonaws.com/ <poolId>",client_id,redirect_uri,response_type="code"(PKCE S256),scope="openid email profile",onSigninCallback(strip?code&state, or silent renew breaks),userStore: new WebStorageStateStore({ store: window.localStorage })(to survive SPA navigations).useAuth()returns{isLoading, isAuthenticated, user, error, signinRedirect, signoutRedirect, removeUser}. Tokens live atauth.user?.id_token,.access_token,.profile.- Send the ID token to an API Gateway JWT authorizer with
audience=<client_id>— the ID token'saudequals the client id, while the access token has noaud, onlyclient_idand scope. - Cognito's
/logoutis on the hosted-UI domain (https://<domain>.auth.<region>.amazoncognito.com/logout?client_id=..&logout_uri=..), not the issuer host. - Vite MPA:
build.rollupOptions.input = { name: resolve(...html), ... }.