Outcome Engineering turns messy product thinking into an Outcome Graph that humans and agents can challenge, trace, and update: vision, strategy, ideal customer profiles (ICPs), customer jobs, outcomes, opportunities, solutions, assumption tests, PRDs — plus a flywheel describing the causal loop that compounds. Jobs (jobs-to-be-done) capture the progress a customer seeks in a specific circumstance; outcomes and opportunities reference them alongside ICPs.
It is a multi-tenant web application:
- Postgres stores the graphs.
- One FastAPI service (
api/) is the single read/write path. It enforces the model at mutation time: placement rules, slug uniqueness, strategy date rules, cascade-delete protection, optimistic concurrency (version + 409). - SvelteKit frontend (
frontend/) — an Excalidraw-style canvas: strategic overview (vision / strategy / ICPs / outcomes), focus into an outcome's trace subtree, side-panel markdown editing, model-constrained child creation, flywheel view, graph sharing. - MCP server (read-only) so agents can list graphs, read trees and nodes, fetch context, and validate.
oeCLI (read-only) with browser login and the agent-skill installer.- Generic OIDC auth: oauth2-proxy in front of the frontend, JWT (JWKS) validation in the API. Auth0 is just the documented example provider.
Requires Docker.
docker compose up --build
open http://localhost:3000The stack runs in simulation auth: sign in with any token (e.g. dev) —
each distinct token is its own dev user. Create a graph, add a vision,
strategy, ICPs, jobs and outcomes, drill into opportunities → solutions →
assumption tests / PRDs, add a flywheel, and share a public read-only link
from the graph's settings page (works in an incognito window).
Services:
| Service | URL |
|---|---|
| frontend | http://localhost:3000 |
| api | http://localhost:8000 |
| mcp | http://localhost:8001/mcp |
Backend (Python 3.11+, uv):
cd api
uv sync
uv run pytest # domain + store + API + CLI + MCP tests
OE_DATABASE_URL=sqlite:///dev.db uv run uvicorn --factory oe_api.app:mainFrontend (Node 22+):
cd frontend
npm install
cp .env.example .env # PUBLIC_API_BASE_URL
npm run devThe API defaults to OE_AUTH_MODE=simulation, so any bearer token works
locally. Alembic migrations run automatically on API startup.
api/src/oe_core/ pure domain: kinds, placement rules, validation,
selector resolution, context assembly
api/src/oe_store/ SQLAlchemy models, Alembic migrations, GraphStore
api/src/oe_api/ FastAPI app: routes, auth, authz, settings
api/src/oe_mcp/ FastMCP read-only tools (proxies the API)
api/src/oe_cli/ Typer CLI (`oe`) + bundled agent skills
frontend/ SvelteKit (adapter-node) UI
uv tool install outcome-engineering # or: uv run oe ... from api/
oe config --api-url https://oe.example.com
oe login # OIDC device flow (browser)
oe login --token dev # simulation-mode servers
oe graphs
oe tree -g my-graph
oe list -g my-graph --kind outcome
oe show outcome.activation -g my-graph
oe trace solution.wizard -g my-graph
oe context solution.wizard -g my-graph # agent-facing markdown context
oe validate -g my-graph
oe install --skills # install agent skills into ./.claude/skills
oe install-skill --agent all # install into ~/.claude + ~/.codexSelectors are <kind>.<slug>, a bare slug (if unique), or a node uuid.
OE_API_URL, OE_TOKEN, and OE_GRAPH env vars override the stored config
(~/.config/oe/).
The mcp service exposes read-only tools (list_graphs, get_tree,
list_nodes, show_node, get_context, get_trace, validate_graph,
get_flywheel) over streamable HTTP. Add it to Claude Code:
claude mcp add --transport http outcome-engineering http://localhost:8001/mcp \
--header "Authorization: Bearer dev"The bearer token is forwarded to the API and validated there (simulation or OIDC, same as any client).
The compose file has no reverse proxy and no TLS — Coolify provides both.
- Create a Docker Compose resource pointing at this repository.
- Set env vars:
POSTGRES_PASSWORD,PUBLIC_API_BASE_URL(the public URL of the API, e.g.https://oe-api.example.com),OE_CORS_ORIGINS(the frontend's public URL), and the OIDC vars below. - Expose
frontend(3000) andapi(8000) through Coolify domains. Exposemcp(8001) if agents should reach it. - To require login in front of the web UI, enable the auth profile
(
COMPOSE_PROFILES=auth) and route the frontend domain atoauth2-proxy(4180) instead offrontend.
No Auth0-specific code exists — any OIDC provider works (Keycloak, Google, GitHub via an OIDC bridge, Entra ID, ...). Auth0 as the worked example:
-
API: In Auth0, create an API (identifier = your audience, e.g.
https://oe-api.example.com). Configure the api service:OE_AUTH_MODE=oidc OE_OIDC_ISSUER=https://YOUR_TENANT.auth0.com/ OE_OIDC_JWKS_URL=https://YOUR_TENANT.auth0.com/.well-known/jwks.json OE_OIDC_AUDIENCE=https://oe-api.example.comUsers are upserted on first authenticated request from the JWT claims (
sub,email,name). -
Web UI: Create a Regular Web Application in Auth0 (callback
https://YOUR_APP_DOMAIN/oauth2/callback). Configure oauth2-proxy:COMPOSE_PROFILES=auth OAUTH2_PROXY_CLIENT_ID=... OAUTH2_PROXY_CLIENT_SECRET=... OAUTH2_PROXY_COOKIE_SECRET=$(openssl rand -base64 32 | head -c 32) OAUTH2_PROXY_REDIRECT_URL=https://YOUR_APP_DOMAIN/oauth2/callbackoauth2-proxy forwards the user's access token to the frontend, which uses it as the API bearer.
-
CLI: Create a Native application in Auth0 with the Device Code grant enabled. Then:
OE_OIDC_ISSUER=https://YOUR_TENANT.auth0.com \ OE_OIDC_CLIENT_ID=... \ OE_OIDC_AUDIENCE=https://oe-api.example.com \ oe login
Tokens (incl. refresh token) are stored in
~/.config/oe/and refreshed automatically.
For Keycloak/Google, substitute the issuer/JWKS/client values; the flow is identical because everything speaks plain OIDC.
Each graph has members with a role: owner (manage members, share links, rename/delete the graph), editor (create/edit/delete nodes and the flywheel), viewer (read-only). Owners can mint public share links — unguessable URLs that grant read-only access to that one graph without login; revoke them any time from graph settings.
All under /api, JSON, Authorization: Bearer <token> (or
X-Share-Token/?share= for share links). Highlights:
GET/POST /graphs,GET/PATCH/DELETE /graphs/{g}GET /graphs/{g}/overview|tree|validateGET/POST /graphs/{g}/nodes,GET/PATCH/DELETE /graphs/{g}/nodes/{selector}(PATCH requiresversion; a stale version returns 409)GET /graphs/{g}/nodes/{selector}/trace|contextGET /graphs/{g}/members,PUT/DELETE /graphs/{g}/members/{user}(owner)GET/POST /graphs/{g}/share-links,DELETE .../share-links/{id}(owner)GET/PUT/DELETE /graphs/{g}/flywheel, CRUD under/flywheel/nodesGET /share/{token}— public share-token resolver
Interactive docs at /docs on the API service.