awslabs / awslabs/loom

Add LangGraph framework

Open
#33 0 comments 0 reactions 0 assignees View on GitHub
enhancement
Dominant language
Python
Stars
183
Forks
41
Avg merge
8h 6m
Merged PRs (30d)
2

Description

## Overview

Loom's custom-code agent path (`deploymentType: "custom"` on the frontend, `source: "deploy"` on the backend) currently supports two interchangeable framework implementations — `agents/strands_agent/` (Strands Agents SDK) and `agents/adk_agent/` (Google ADK), selected via the `agent_framework` field (`"strands"` | `"adk"`, default `"strands"`). Add LangGraph as a third alternate framework for this same custom-code path, reaching the same behavioral and operational parity that ADK reached with Strands.

Note on terminology: "harness" in this codebase refers specifically to AWS's own no-code managed agent runtime (`source: "harness"`, `backend/app/services/harness.py`, wrapping AgentCore's native `CreateHarness`/`InvokeHarness` APIs) — a completely separate deployment mode with no user-supplied code. This issue targets the custom-code path (`source: "deploy"`), not the managed harness path.

## Context

### Current State
- Two custom-code implementations exist side by side: `agents/strands_agent/` and `agents/adk_agent/`, each mirroring the same directory structure (`src/handler.py`, `src/agent.py`, `src/config.py`, `src/telemetry.py`, `src/integrations/{approval,mcp_client,a2a_client,memory,secrets,code_interpreter}.py`, `requirements.txt`)
- `build_agent_artifact()` in `backend/app/services/deployment.py` (line 98) takes an `agent_framework: str = "strands"` parameter and resolves the source directory via the `AGENT_SOURCE_DIRS` dict (line 24): `{"strands": agents/strands_agent, "adk": agents/adk_agent}`
- The `Agent` ORM model (`backend/app/models/agent.py`, line 44) has `agent_framework = Column(String, nullable=True) # 'strands' or 'adk'`
- `AgentCreateRequest`/`AgentDeployRequest` (`backend/app/routers/agents.py`, line 169) has `agent_framework: str = Field(default="strands", description="Custom-code agent framework: 'strands' or 'adk'. Only used for source='deploy'.")`; the deploy pipeline passes it through to `build_agent_artifact()` (line 1302) and re-derives it on redeploy (line 1746: `agent.agent_framework or "strands"`)
- `frontend/src/api/types.ts` has `agent_framework: string | null` on `AgentResponse` (line 25) and `agent_framework?: string` on the deploy request type (line 110)
- `AgentRegistrationForm.tsx` renders an "Agent Framework" radio selector (lines 921-950) with two options, `"strands"` → "Strands Agent" and `"adk"` → "Google ADK", visible only when `deploymentType === "custom"`; JSON export/import round-trips `agent_framework` (lines 292, 594, 697, 814)

### Key Files
- `agents/strands_agent/` and `agents/adk_agent/` — the two existing implementations; both are parity references for the new LangGraph implementation
- `backend/app/services/deployment.py` — `AGENT_SOURCE_DIRS` dict (line 24), `build_agent_artifact()` (line 98), `create_runtime()`/`create_runtime_endpoint()` (entrypoint command construction)
- `backend/app/models/agent.py` — `Agent.agent_framework` column (line 44)
- `backend/app/routers/agents.py` — `AgentCreateRequest`/`AgentDeployRequest.agent_framework` field (line 169), deploy/redeploy call sites (lines 976, 1302, 1746), export serialization (line 3906-3907)
- `frontend/src/api/types.ts` — `AgentResponse.agent_framework`, deploy request `agent_framework` field
- `frontend/src/components/AgentRegistrationForm.tsx` — Agent Framework radio selector (lines 921-950), export/import handling (lines 292, 594, 697, 814)

### Technology Stack
- **Agent Runtime**: AWS Bedrock AgentCore (Python 3.13, ARM64 container, `manylinux2014_aarch64` wheels)
- **Existing Frameworks**: Strands Agents SDK, Google ADK
- **Proposed Addition**: LangGraph
- **Backend**: Python, FastAPI, SQLAlchemy, SQLite
- **Frontend**: TypeScript, React, Vite, shadcn/ui, Tailwind CSS
- **Telemetry**: OpenTelemetry API, `aws-opentelemetry-distro` (`opentelemetry-instrument` wrapper)
- **Packaging**: pip install to temp dir targeting `manylinux2014_aarch64`/Python 3.13, zip, upload to S3

### References
- Strands Agents SDK: existing implementation in `agents/strands_agent/`
- Google ADK: existing implementation in `agents/adk_agent/`
- LangGraph: https://langchain-ai.github.io/langgraph/

## Requirements

### R1: LangGraph implementation at parity with Strands Agents and Google ADK

Users who select the LangGraph framework must get the same configuration surface and operational behavior as the existing Strands and ADK implementations — no feature should be framework-specific unless LangGraph genuinely cannot support it (and any such gap must be documented).

- Create a new agent source directory `agents/langgraph_agent/` mirroring the structure of `agents/strands_agent/` and `agents/adk_agent/`:
- `src/handler.py` — AgentCore Runtime entrypoint using `BedrockAgentCoreApp`, implementing both the `@app.entrypoint` streaming-SSE contract and the `@app.websocket` bidirectional contract with the same event shapes the existing implementations emit (`{"tool_use": {...}}`, `{"interrupt": {...}}`, `{"elicitation": {...}}`, `{"token_info": {...}}`, `{"_error": ...}`, plain-string text chunks) so the existing SSE/WebSocket invocation pipeline in `backend/app/routers/invocations.py` requires no changes to consume it
- `src/agent.py` — builds a LangGraph graph/model equivalent to `_build_model()`/`build_agent()`, supporting the same model providers (Bedrock, OpenAI, Anthropic, LiteLLM) driven by the same `provider`/`base_url`/`api_key_secret_arn` config fields
- `src/config.py` — reuses the same `AGENT_CONFIG_JSON` schema (`AgentConfig`, `MCPServerConfig`, `A2AAgentConfig`, `MemoryConfig`, `CodeInterpreterConfig`, `IntegrationsConfig`, `AuthConfig`) via `load_config()`, so no backend config-assembly changes are needed beyond selecting which artifact to build
- `src/integrations/` — LangGraph-native equivalents of `approval.py` (HITL/interrupt-equivalent, e.g. LangGraph's native interrupt/checkpoint mechanism), `mcp_client.py` (MCP tool attachment + OAuth2/OBO token exchange), `a2a_client.py` (remote A2A agents as callable tools/nodes), `memory.py` (AgentCore Memory integration), `secrets.py` (Secrets Manager resolution), `code_interpreter.py` (Code Interpreter tool)
- `src/telemetry.py` — same OpenTelemetry span structure as Strands/ADK: an `agent.invocation` span wrapping each invocation, `tool.call` and `model.call` spans around tool/model calls, so telemetry consumers (traces UI, CloudWatch) see equivalent data regardless of framework
- `requirements.txt` — `langgraph`/`langchain` package(s), `bedrock-agentcore`, `boto3`, `mcp`, `opentelemetry-api`, `aws-opentelemetry-distro` (same telemetry stack as Strands/ADK)
- Add `"langgraph": _AGENTS_DIR / "langgraph_agent"` to `AGENT_SOURCE_DIRS` in `backend/app/services/deployment.py` (line 24-27)
- `create_runtime()` and `create_runtime_endpoint()` must set the entrypoint command appropriate to LangGraph — reuse `["opentelemetry-instrument", "src/handler.py"]` if the LangGraph handler is instrumentable the same way; document any deviation
- Update the `Agent.agent_framework` column comment (`backend/app/models/agent.py`, line 44) and the `agent_framework` field description on `AgentCreateRequest`/`AgentDeployRequest` (`backend/app/routers/agents.py`, line 169) to include `'langgraph'` as a valid value
- Existing `agents/strands_agent/` and `agents/adk_agent/` code, config schema, and deployment behavior must remain unchanged — this is an additive sibling implementation, not a replacement

### R2: Frontend framework selection

Users deploying a custom-code agent (`deploymentType: "custom"`) must be able to choose LangGraph alongside the existing Strands and ADK options.

- Add a third radio option to the Agent Framework selector in `AgentRegistrationForm.tsx` (lines 921-950): `value="langgraph"` → "LangGraph", alongside "Strands Agent" and "Google ADK"; default selection remains "Strands Agent"
- Selecting "LangGraph" should submit `agent_framework: "langgraph"` in the deploy request
- The rest of the custom-agent form (system prompt, model/provider selection, role, network mode, MCP/A2A/memory/code-interpreter integrations, tags) should behave identically regardless of framework selection, since R1 requires config-schema parity
- Agent list/detail views that display deployment metadata (e.g. `AgentResponse.agent_framework`) should correctly surface `"langgraph"` alongside the existing `"strands"`/`"adk"` values — no frontend code should assume only two framework values exist
- JSON export/import of agent configuration should continue to round-trip `agent_framework: "langgraph"` correctly (existing export/import logic in `AgentRegistrationForm.tsx` at lines 292, 594, 697, 814 is already framework-value-agnostic; verify no hardcoded `"strands" | "adk"` union type blocks `"langgraph"`)

## Testing

- Run backend tests: `cd backend && make test`
- Run frontend typecheck: `cd frontend && npx tsc --noEmit`
- Verify framework selection defaults to "Strands Agent" and existing Strands/ADK deployments are unaffected (regression)
- Verify artifact isolation: a LangGraph deploy packages only `agents/langgraph_agent/` and its own `requirements.txt`, with no cross-contamination from `agents/strands_agent/` or `agents/adk_agent/`
- Verify LangGraph deployment reaches AgentCore `READY` state and responds to `/ping`
- Verify invocation parity against the Strands/ADK baseline for the same agent configuration:
- Streaming text via SSE (`@app.entrypoint`)
- WebSocket MCP elicitation (`@app.websocket`)
- Tool-use / HITL interrupt flow
- MCP tool invocation, including OAuth2/OBO delegation
- A2A agent invocation
- AgentCore Memory read/write
- Verify telemetry parity: traces for a LangGraph invocation show equivalent `agent.invocation`/`tool.call`/`model.call` spans to a Strands/ADK invocation of the same configuration, visible in the existing traces UI
- Verify `agent_framework: "langgraph"` round-trips correctly through JSON export/import

## Out of Scope

- Changes to the managed-harness path (`source: "harness"`, `deploymentType: "managed"`, `backend/app/services/harness.py`) — LangGraph is not being added as a managed-harness option
- Additional agent frameworks beyond LangGraph (e.g. LangChain Deep Agent, OpenDevin — tracked separately)
- Framework-specific UI for monitoring or debugging agent internals beyond existing trace/log views
- Multi-framework composition (chaining agents built on different frameworks)
- Migrating existing Strands- or ADK-based agents to LangGraph

Contributor guide

Open the contributing guide

Research direction

Compare agents/strands_agent/ and agents/adk_agent/ first, then trace AGENT_SOURCE_DIRS and build_agent_artifact() in backend/app/services/deployment.py, the agent_framework fields in backend/app/models/agent.py and backend/app/routers/agents.py, and the selector in frontend/src/components/AgentRegistrationForm.tsx. Run backend make test and the frontend TypeScript check; done means LangGraph artifacts deploy and invoke with the required parity, while existing frameworks and export/import remain unaffected.

Written by the indexing model from the issue text.

Assessment

Tech stack
aws, fastapi, python, react, sqlalchemy, sqlite, typescript, vite
Domain
ai, backend, cloud, frontend, observability, testing-qa
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.