Skip to content
Go back

Monorepo with uv workspaces

Published:  at  18:05

I’m exploring an agentic architecture for an application that will include the following components:

My intution is monorepo will support streamlined development across all components. As a prerequiste - I wanted the following requirements to be satisfied:

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.



Previous Post
clip-path and Scroll animations with vanilla CSS
Next Post
SQLAlchemy's TypeDecorator