I’m exploring an agentic architecture for an application that will include the following components:
- Python backend (Pyramid, FastAPI, etc)
- Langgraph backend (the intelligence and orchestration layer)
- NextJS (Frontend)
My intution is monorepo will support streamlined development across all components. As a prerequiste - I wanted the following requirements to be satisfied:
- Isolation per Python component - Each Python-based component to maintain its own pyproject.toml.
- Shared virtual environment - Have a single .venv that is shared across all Python-based projects. I acknowledge - there is a risk that a single virtual environment may not work for development, long-term, as components may deviate in versions for overlapping packages. I will report back on my experience as the project matures.
Initially, I wanted to use uv
for package mgmt and conda for managing environments. I prefer conda’s centrally managed environments. My discovery was that conda and uv don’t play nicely, if you want uv to also manage pyproject.toml i.e using uv add [package name]
. In this case - uv insists on managing its own environment using virtualenv. Meaning, we can only use uv’s pip interface uv pip
to manually install packages, when using conda.
To enable a single .venv for all Python-based components I came across uv workspaces uv workspaces allows for multiple Python packages/applications to be linked within a single pyproject.toml, additionally I came across Jasper Ginn’s great example of using uv workspaces, which soldified my understanding.
Below you will find excerpts from the monorepo that outline the integration of the two Python-based components(langgraph app and Fastapi backend) using uv workspaces.
Here is an example of the root pyproject.toml that ties all Python-based components together:
[project]
name = "alfred"
version = "0.0.1"
description = "An agent with a tool to save long-term memory."
authors = [
{ name = "Victor Matekole", email = "alfred@matekole.com" },
]
readme = "README.md"
license = { text = "MIT" }
requires-python = ">=3.13"
dependencies = ["memory-agent", "backend", "tqdm>=4,<5"]
# Indicates the dependencies - "memory-agent" and "backend" should be sourced from workspaces, rather than pypi or another registry. #
[tool.uv.sources]
memory-agent = { workspace = true }
backend = { workspace = true }
################################################################################
###### Declare workspace members and their paths to discover pyproject.toml #######
[tool.uv.workspace]
members = [
"agents/alfred-agent/graphs",
"backend",
]
###############################################################################
######################## Shared virtual environment ######################
[tool.uv]
dev-dependencies = [
"pre-commit>=3.8.0",
"pytest>=8.3.2",
# These are added as dev dependencies because they should be available
# when developing the project.
"memory-agent",
"backend",
]
package = false
###########################################################################
[tool.ruff]
lint.ignore = ["E501"]
extend-exclude = [
"__pycache__",
".eggs",
".git",
".venv",
"build",
"dist",
"notebooks",
".cache"
]
line-length = 80
(Langgraph) backend pyproject.toml:
[project]
#########################Package/Workspace name###########################
name = "memory-agent"
##########################################################################
version = "0.0.1"
description = "An agent with a tool to save long-term memory."
authors = [
{ name = "Victor Matekole", email = "alfred@matekole.com" },
]
readme = "README.md"
license = { text = "MIT" }
requires-python = ">=3.13"
dependencies = [
"langgraph>=0.2.34,<0.4.2",
# Optional (for selecting different models)
"langchain-openai>=0.2.1",
"langchain-anthropic>=0.2.1",
"langchain-core>=0.3.8",
"python-dotenv>=1.0.1",
"langgraph-sdk>=0.1.32",
"langgraph-cli[inmem]>=0.2.10",
"langchain>=0.3.9",
]
[project.optional-dependencies]
dev = ["mypy>=1.11.1", "ruff>=0.6.1", "pytest-asyncio"]
[build-system]
requires = ["setuptools>=73.0.0", "wheel"]
build-backend = "setuptools.build_meta"
[tool.setuptools]
packages = ["memory_agent"]
[tool.setuptools.package-dir]
"memory_agent" = "src/memory_agent"
"langgraph.templates.memory_agent" = "src/memory_agent"
[tool.setuptools.package-data]
"*" = ["py.typed"]
[tool.ruff]
lint.select = [
"E", # pycodestyle
"F", # pyflakes
"I", # isort
"D", # pydocstyle
"D401", # First line should be in imperative mood
"T201",
"UP",
]
lint.ignore = ["UP006", "UP007", "UP035", "D417", "E501"]
include = ["*.py", "*.pyi", "*.ipynb"]
[tool.ruff.lint.per-file-ignores]
"tests/*" = ["D", "UP"]
"ntbk/*" = ["D", "UP", "T201"]
[tool.ruff.lint.pydocstyle]
convention = "google"
[tool.mypy]
ignore_errors = true
[tool.isort]
profile = "black"
multi_line_output = 4
include_trailing_comma = true
force_grid_wrap = 0
use_parentheses = true
line_length = 80
[dependency-groups]
test = [
"pytest>=8.3.5",
"pytest-asyncio",
]
Python backend pyproject.toml:
[project]
#########################Package/Workspace name###########################
name = "backend"
##########################################################################
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"fastapi>=0.115.12",
]
File structure:
alfred/
├── .venv/ # Python virtual environment
├── .vscode/ # VS Code configuration
├── agents/ # Agent components
│ └── alfred-agent/ # langgraph application (intelligence layer)
│ ├── pyproject.toml # Intelligence layer pyproject.toml
├── backend/ # Backend service (core backend)
│ ├── main.py # Main backend application
│ ├── pyproject.toml # Backend dependencies pyproject.toml
├── frontend/ # Frontend applications
│ └── agent-chat-ui/ # Agent chat user interface, NextJS app
├── repos/ # External repositories, for reference.
├── .gitignore # Git ignore configuration
├── .pre-commit-config.yaml # Pre-commit hooks configuration
├── pyproject.toml # Root pyproject.toml, brings all dependencies together
├── README.md # Main project documentation (this file)
└── uv.lock # Dependency lock file
With all this setup, you can now run from the project root uv sync
or uv run
.