Documentation

Development

This section is for contributors working on Personal Agent itself. It complements the Run locally guide — that page gets the stack up; this section explains how the codebase is laid out and how to extend it.

Workspace layout

The code is split across separate repos under the personal-agent-org org. The backend repo (personal-agent-org/backend) is the FastAPI app, the Temporal worker, the contracts, the integrations and the tools in one personal_agent package (src/ layout).

Repo / path What it is
backend repo, src/personal_agent/contracts/ The single source of truth shared by API + worker: IDs, the RunSpec/ToolsetSnapshot, AG-UI events, control frames, usage, keys, world-memory and workflow-trigger contracts.
backend repo, src/personal_agent/ The FastAPI app (personal_agent). App factory personal_agent.main:create_app (app = create_app()); subpackages for config, DB, auth, the agent, toolset assembly, realtime, integrations, workflows and more.
backend repo, src/personal_agent/worker/ The Temporal worker (personal_agent.worker) - the durable ChatAgentWorkflow plus the curator / goal / workflow-schedule / entity-sync / world-maintenance / proactive-review / push-token-maintenance workflows and their activities.
backend repo, integrations/<domain>/ Home-Assistant-style integration folders (manifest + config flow + integration class), discovered at runtime by the IntegrationRegistry.
personal-agent-org/frontend The Quasar / Vue 3 single-page app (at the repo root). Android has a separate shell repo.
clients and capability providers personal-agent-org/pa contains the desktop and terminal chat clients. personal-agent-org/computer-service exposes tools and sensors with no chat access. Browser devices live in browser-sandbox and browser-extension.

Ops live in the deploy repo (personal-agent-org/deploy): compose/, charts/ (Helm), keycloak/ (realm-as-code) and observability/. The backend repo also carries tools/ (scripts).

!!! note “Conventions” Python 3.12, async SQLAlchemy 2.0 + asyncpg. Linting is ruff (line length 100), types are checked with pyright. All hard-coded backend strings are English; user-facing language comes from the model and frontend i18n. IDs are time-ordered UUIDv7; run_id = run:{uuidv7} is the cross-transport key.

Two run paths, one envelope

A chat turn executes on one of two paths, but both emit the same AG-UI events onto a per-run Redis Stream, which the server relays to the client over SSE.

Inline Durable
Where it runs A FastAPI background task (realtime/producers/inline.py) A Temporal workflow (ChatAgentWorkflow in src/personal_agent/worker/)
Streaming agent.run(..., event_stream_handler=...) feeding the shared AgUiConverter The Temporal model activity feeds the same AgUiConverter
Used for Short, interactive turns Long-running / durable runs that must survive restarts

api/routers/runs.py (_launch_run) is the shared chokepoint that decides INLINE vs DURABLE and builds the RunSpec. The tools available to a run are snapshotted into the RunSpec at run start — the workflow never queries live DB state during a run or replay.

!!! warning “Streaming is one envelope” AG-UI is the only streaming envelope, on the Redis bus and on the SSE wire. Both paths run the agent through the same AgUiConverter, so inline and durable runs render identically. The pydantic-ai run_stream* / iter helpers are forbidden inside a Temporal workflow.

Dev task runner

Each repo uses just - running just (or just --list) in a repo shows its recipes. A dev-from-source stack spans the deploy, backend and frontend repos.

Bring up the dev infra (Postgres / Redis / Temporal / Keycloak) from the deploy repo:

docker compose -f compose/docker-compose.yml up    # in personal-agent-org/deploy

Then, in the backend repo (personal-agent-org/backend):

just setup      # uv sync + install git hooks
just migrate    # alembic upgrade head

# in separate terminals:
just api        # run the API (uvicorn --reload) - needs the infra up + just migrate
just worker     # run the Temporal worker

just test       # pytest (some tests need PG + Redis); `just test-unit` for fast tests only
just check      # pre-PR gate: lint + types + test-unit

And the Quasar dev server from the frontend repo (personal-agent-org/frontend):

just setup      # pnpm install + git hooks
just dev        # run the Quasar dev server
just check      # pre-PR gate: lint + i18n + test + build

just check is the gate to run before opening a PR (in each repo). Other useful backend recipes include just lint, just fmt, just types, and just migration "msg" to autogenerate a migration. Read the justfile in each repo for the exact command behind any recipe.

!!! note “Tests run from the repo root” The e2e tests (requires_services) need local Postgres + Redis at DSN postgresql+asyncpg://personal_agent:personal_agent@localhost:5432/personal_agent. pytest is asyncio_mode = "auto", so async tests need no decorator.

Where to start

The most common contribution is a new integration — a self-contained folder that declares its capabilities and is discovered at runtime, with no changes to the core app. Start here:

  • Integrations — the folder layout (manifest + config flow + integration class) and how the IntegrationRegistry discovers them.
  • Integration capabilities — the capability providers an integration can declare (message reader/sender/listener, web search/fetch, weather, compute) and the entity types it contributes.
  • Config flows — how an integration collects and validates its setup input.

Beyond integrations, two more extension surfaces live here:

  • Skills — user-authored capability packages with progressive disclosure.
  • Surfaces — composable views (chat / editor / terminal) that integrations and the core app can contribute.

For the invariants that hold the run paths and tenancy together, see the Frozen contracts.