An enterprise AI-agent platform composed of independently-deployable applications: identity, agent registry, prompt composition, LLM cost optimization, workflow orchestration, an MCP execution engine with embedded LLM gateway, a federated lookup + receipt + event-bus platform layer, and a unified portal that wraps them all.
Published as a monorepo:
https://github.com/ashokraj2011/singularity-platform
- Docker Desktop (Compose v2)
git,curl,psql(optional, for ad-hoc inspection)- Ports free:
3000, 5174, 5175, 5180, 7100, 8000-8003, 8080, 8100-8101, 8500, 5432, 5433, 5434, 5436, 9000-9001 - ~6 GB free RAM for the full stack
git clone https://github.com/ashokraj2011/singularity-platform.git
cd singularity-platform./singularity.sh upThis brings up:
- Master stack (19 services): IAM + agent-and-tools (4 svcs) + context-fabric (4 svcs) + workgraph (api + web + pg + minio) + mcp-server-demo + portal + user-and-capability
- Pseudo-IAM (port 8101): the SPA's auth authority — accepts any email/password, mints a JWT signed with the same secret as workgraph-api
- Audit & governance ledger (port 8500): the cross-service event ledger every producer fires into
First boot pulls images + builds web bundles. Wait ~3–5 minutes. Tail with ./singularity.sh logs workgraph-api -f if you want to watch.
Need to bring up just one piece?
./singularity.sh up <service-name>works for the master-stack services (run./singularity.sh lsfor the list). The two side stacks (pseudo-iam, audit-governance) only come up via the no-arg form.
( cd agent-and-tools/apps/agent-runtime \
&& DATABASE_URL="postgresql://postgres:singularity@localhost:5432/singularity" \
npx prisma db seed )Lands 9 agent templates (4 locked common baselines + 5 derived), 5 prompt profiles, 7 layers, 4 tools, 1 default capability.
for u in \
"http://localhost:8101/health" \
"http://localhost:8500/health" \
"http://localhost:7100/health" \
"http://localhost:8000/health" \
"http://localhost:8080/health" \
"http://localhost:3000/api/runtime/agents/templates?scope=common&limit=3" \
"http://localhost:5174/"; do
printf "%-65s %s\n" "$u" "$(curl -s -o /dev/null -w '%{http_code}' $u)"
doneYou should see 200 for all seven.
| Step | URL | What to show |
|---|---|---|
| 1. Login | http://localhost:5174 |
Auto-login fires through pseudo-IAM. Mention: "any user, anywhere — same JWT secret as our IAM, swap the env var to point at real IAM" |
| 2. Agent Studio | http://localhost:3000/agent-studio → pick the seeded capability from the dropdown |
Show the four Locked common baselines (Architect / Developer / QA / Governance), click Derive → on one, name it. Mention: "derived agents inherit prompt profile + tool policy, become editable by capability owners, audit-gov captures agent.template.derived" |
| 3. Run a workflow | localhost:5174/runs → click Run a Workflow → pick "Business Initiative Delivery" → start |
The new run lands in /runs/<id>. Open a HUMAN_TASK node, attach a file, click Complete. Workflow advances. |
| 4. Run Insights | Click the green Insights → pill at the top of the run viewer | The M24 dashboard — total duration, per-step Gantt with precise timing (startedAt/completedAt columns), artifacts list, cost-by-model, full audit timeline keyed to the run. Mention: "every step duration is authoritative, not inferred" |
| 5. Governance & cost | http://localhost:3000/audit and http://localhost:3000/cost |
Cross-service ledger. Show the recent agent.template.derived, cf.execute.completed, tool.execution.success, llm.call.completed rows. Then /cost for $$ + tokens, with model breakdown. Mention: "all four producers — mcp-server, workgraph-api, tool-service, context-fabric, agent-runtime — fire fire-and-forget events here; pre-flight budget/rate-limit checks happen inline." |
- Set a tight budget then watch DENIED:
Re-run any AGENT_TASK on that capability —
curl -s -X POST http://localhost:8500/api/v1/governance/budgets \ -H 'content-type: application/json' \ -d '{"scope_type":"capability","scope_id":"<cap-id>","period":"day","tokens_max":1}'
status:DENIEDreturns instantly, no LLM dispatch. Open/audit→ seegovernance.deniedevent. - MCP smoke (slick because it's the same call workflows make under the hood):
curl -sS -X POST http://localhost:7100/mcp/invoke \ -H 'authorization: Bearer demo-bearer-token-must-be-min-16-chars' \ -H 'content-type: application/json' \ -d '{"runContext":{"traceId":"t-demo","runId":"r-demo","capabilityId":"c-demo"},"message":"hi","tools":[]}'
- Insights for a workflow that calls an LLM: design a workflow with an AGENT_TASK, point it at a derived agent, run. Insights will populate
cost_usd+tokens+ model breakdown for real.
./singularity.sh down # stop all three stacks, keep data volumes
./singularity.sh nuke # stop + WIPE all data volumes (asks for confirmation)Workgraph SPA http://localhost:5174 runs, designer, insights
Agent / Tools SPA http://localhost:3000 Agent Studio, /audit, /cost
Singularity Portal http://localhost:5180 branded wrapper around all of it
User & Capability SPA http://localhost:5175 IAM admin
Workgraph API http://localhost:8080
Agent Runtime API http://localhost:3003
Tool Service API http://localhost:3002
Prompt Composer API http://localhost:3004
Context Fabric API http://localhost:8000
MCP Server http://localhost:7100
Pseudo-IAM http://localhost:8101
Audit & Governance API http://localhost:8500
- First boot of agent-runtime fails seed if
pgvectorextension isn't created. The compose'sat-postgresis a pgvector image so this is usually OK; if you seetype "vector" does not existafter a force-reset:docker exec agentandtools-postgres psql -U postgres -d singularity -c "CREATE EXTENSION IF NOT EXISTS vector;"
- Token errors after a long idle: pseudo-IAM signs 24h JWTs. If your tab sat overnight, hard-refresh
localhost:5174and let auto-login mint a fresh one. - Port collisions —
lsof -i :5174if the workgraph SPA won't start; another stack from a previous demo might still be holding it.
The narrative to lead with: "Singularity is a governed agent platform — every agent is rooted in a locked baseline, every workflow run is observable end-to-end, and every LLM call is gated against a budget."
For dev machines that already have Postgres and don't want Docker. Focused on the demo path; skips real IAM (uses pseudo-IAM), the three optional context-fabric siblings, and MinIO (file uploads will fail but everything else works).
bin/bare-metal.sh up <db_user> [db_password] [db_host] [db_port]
bin/bare-metal.sh smoke # curl every /health endpoint
bin/bare-metal.sh status # list running PIDs
bin/bare-metal.sh logs workgraph-api # tail one service
bin/bare-metal.sh down # stop everything + free portsIdempotent — re-runs of up skip installs and DB creation if they already happened, just re-boots. Defaults: db_password from $PGPASSWORD env or postgres, db_host=localhost, db_port=5432.
The manual recipe below is what the script does under the hood — useful if you want to step through it or diverge.
Adjust user/password to match your instance (defaults: postgres@localhost:5432).
psql postgres <<'SQL'
CREATE DATABASE singularity;
CREATE DATABASE workgraph;
CREATE DATABASE audit_governance;
CREATE DATABASE singularity_iam;
\c singularity
CREATE EXTENSION IF NOT EXISTS vector;
CREATE EXTENSION IF NOT EXISTS pgcrypto;
\c audit_governance
CREATE EXTENSION IF NOT EXISTS pgcrypto;
SQL
# Apply audit-gov schema (7 tables + rate_card seed)
psql -d audit_governance -f audit-governance-service/db/init.sqlcat > .env.local <<'EOF'
export PG_HOST=localhost
export PG_PORT=5432
export PG_USER=postgres
export PG_PASS=postgres
export DATABASE_URL_AGENT_TOOLS="postgresql://${PG_USER}:${PG_PASS}@${PG_HOST}:${PG_PORT}/singularity"
export DATABASE_URL_WORKGRAPH="postgresql://${PG_USER}:${PG_PASS}@${PG_HOST}:${PG_PORT}/workgraph"
export DATABASE_URL_AUDIT_GOV="postgresql://${PG_USER}:${PG_PASS}@${PG_HOST}:${PG_PORT}/audit_governance"
# Shared JWT secret (32+ chars — workgraph-api enforces)
export JWT_SECRET="dev-secret-change-in-prod-min-32-chars!!"
# IAM points at pseudo (skip real IAM)
export AUTH_PROVIDER="iam"
export IAM_BASE_URL="http://localhost:8101/api/v1"
# Cross-service URLs
export AUDIT_GOV_URL="http://localhost:8500"
export PROMPT_COMPOSER_URL="http://localhost:3004"
export AGENT_RUNTIME_URL="http://localhost:3003"
export TOOL_SERVICE_URL="http://localhost:3002"
export AGENT_SERVICE_URL="http://localhost:3001"
export CONTEXT_FABRIC_URL="http://localhost:8000"
export MCP_SERVER_URL="http://localhost:7100"
export MCP_BEARER_TOKEN="demo-bearer-token-must-be-min-16-chars"
# LLM mock by default — no API keys required
export LLM_PROVIDER="mock"
export LLM_MODEL="mock-fast"
EOF
source .env.local( cd agent-and-tools && npm install )
( cd workgraph-studio && pnpm install )
( cd pseudo-iam-service && npm install )
( cd audit-governance-service && npm install )
( cd mcp-server && npm install )
# Prisma push + seed for agent-runtime
( cd agent-and-tools/apps/agent-runtime \
&& DATABASE_URL="$DATABASE_URL_AGENT_TOOLS" npx prisma db push \
&& DATABASE_URL="$DATABASE_URL_AGENT_TOOLS" npx prisma db seed )
# Prisma push for workgraph-api
( cd workgraph-studio/apps/api \
&& DATABASE_URL="$DATABASE_URL_WORKGRAPH" npx prisma db push )
# Python deps for context-api
pip install fastapi uvicorn httpx pydantic-settings python-jose[cryptography] \
sqlalchemy aiosqliteSaves PIDs to .pids so a single kill cleans up.
mkdir -p logs && : > .pids
boot() {
local name=$1; shift
( "$@" >> "logs/${name}.log" 2>&1 & echo $! >> .pids ) && \
echo "→ $name (PID $!) → tail -f logs/${name}.log"
}
# ---- Tier 0: zero-dep ----
boot pseudo-iam bash -c "cd pseudo-iam-service && JWT_SECRET=\"$JWT_SECRET\" PORT=8101 npm run dev"
boot audit-gov bash -c "cd audit-governance-service && DATABASE_URL=\"$DATABASE_URL_AUDIT_GOV\" PORT=8500 npm run dev"
sleep 2
# ---- Tier 1: agent-and-tools backend ----
boot agent-service bash -c "cd agent-and-tools/apps/agent-service && PORT=3001 AUDIT_GOV_URL=\"$AUDIT_GOV_URL\" JWT_SECRET=\"$JWT_SECRET\" npm run dev"
boot tool-service bash -c "cd agent-and-tools/apps/tool-service && PORT=3002 DATABASE_URL=\"$DATABASE_URL_AGENT_TOOLS\" AUDIT_GOV_URL=\"$AUDIT_GOV_URL\" JWT_SECRET=\"$JWT_SECRET\" npm run dev"
boot agent-runtime bash -c "cd agent-and-tools/apps/agent-runtime && PORT=3003 DATABASE_URL=\"$DATABASE_URL_AGENT_TOOLS\" AUDIT_GOV_URL=\"$AUDIT_GOV_URL\" JWT_SECRET=\"$JWT_SECRET\" npm run dev"
boot prompt-composer bash -c "cd agent-and-tools/apps/prompt-composer && PORT=3004 DATABASE_URL=\"$DATABASE_URL_AGENT_TOOLS\" AUDIT_GOV_URL=\"$AUDIT_GOV_URL\" JWT_SECRET=\"$JWT_SECRET\" npm run dev"
# ---- Tier 2: execution + orchestrator ----
boot mcp-server bash -c "cd mcp-server && PORT=7100 MCP_BEARER_TOKEN=\"$MCP_BEARER_TOKEN\" LLM_PROVIDER=mock LLM_MODEL=mock-fast AUDIT_GOV_URL=\"$AUDIT_GOV_URL\" npm run dev"
boot context-api bash -c "cd context-fabric/services/context_api_service && DATABASE_URL=\"$DATABASE_URL_AUDIT_GOV\" PORT=8000 AUDIT_GOV_URL=\"$AUDIT_GOV_URL\" MCP_SERVER_URL=\"$MCP_SERVER_URL\" MCP_BEARER_TOKEN=\"$MCP_BEARER_TOKEN\" uvicorn app.main:app --host 0.0.0.0 --port 8000"
sleep 3
# ---- Tier 3: workgraph ----
boot workgraph-api bash -c "cd workgraph-studio/apps/api && PORT=8080 DATABASE_URL=\"$DATABASE_URL_WORKGRAPH\" JWT_SECRET=\"$JWT_SECRET\" AUTH_PROVIDER=iam IAM_BASE_URL=\"$IAM_BASE_URL\" AGENT_RUNTIME_URL=\"$AGENT_RUNTIME_URL\" TOOL_SERVICE_URL=\"$TOOL_SERVICE_URL\" AGENT_SERVICE_URL=\"$AGENT_SERVICE_URL\" PROMPT_COMPOSER_URL=\"$PROMPT_COMPOSER_URL\" CONTEXT_FABRIC_URL=\"$CONTEXT_FABRIC_URL\" AUDIT_GOV_URL=\"$AUDIT_GOV_URL\" npm run dev"
# ---- Tier 4: SPAs ----
boot agent-web bash -c "cd agent-and-tools/web && AUDIT_GOV_URL=\"$AUDIT_GOV_URL\" AGENT_RUNTIME_URL=\"$AGENT_RUNTIME_URL\" TOOL_SERVICE_URL=\"$TOOL_SERVICE_URL\" AGENT_SERVICE_URL=\"$AGENT_SERVICE_URL\" PROMPT_COMPOSER_URL=\"$PROMPT_COMPOSER_URL\" npm run dev"
boot workgraph-web bash -c "cd workgraph-studio/apps/web && VITE_API_BASE=http://localhost:8080 VITE_PSEUDO_IAM_URL=http://localhost:8101/api/v1 VITE_AUTO_LOGIN=1 npm run dev"
echo
echo "All booted. Tail any service with: tail -f logs/<name>.log"
echo "Stop everything: kill \$(cat .pids) && rm .pids"for url in \
http://localhost:8101/health \
http://localhost:8500/health \
http://localhost:7100/health \
http://localhost:8000/health \
http://localhost:8080/health \
"http://localhost:3000/api/runtime/agents/templates?scope=common&limit=3" \
http://localhost:5174/ \
; do
printf "%-65s %s\n" "$url" "$(curl -s -o /dev/null -w '%{http_code}' "$url")"
doneAll seven should return 200. Open http://localhost:5174 for the demo.
kill $(cat .pids) && rm .pids
# Optional — wipe data:
psql postgres -c "DROP DATABASE singularity; DROP DATABASE workgraph; DROP DATABASE audit_governance; DROP DATABASE singularity_iam;"| Skipped | Impact |
|---|---|
real IAM (iam-service) |
None for demo — pseudo-IAM signs JWTs workgraph accepts because they share JWT_SECRET |
| llm-gateway, context-memory, metrics-ledger | None — context-api calls mcp-server directly; mcp-server's embedded LLM is mock |
| MinIO | File uploads return 5xx; insights, Agent Studio, audit, cost all still work |
portal (:5180), user-and-capability (:5175) |
Optional UI wrappers; :5174 + :3000 cover the demo path |
Common boot failures: (a) wrong PG_USER/PG_PASS, (b) pgvector extension not installed in singularity, (c) port collision (lsof -i :3003).
The platform layer (M11) and supporting milestones landed as a cohesive set; everything below is shipped + smoke-tested end-to-end.
| Milestone | What it added | Verification |
|---|---|---|
| M9.z Approval pause/resume | MCP /mcp/resume + cf /execute/resume + workgraph AgentRunStatus.PAUSED + POST /agent-runs/:id/approve. Single-use continuation tokens, 24h TTL. |
Full workflow → tool requires_approval → workgraph PAUSED → UI approve → MCP resumes loop → AWAITING_REVIEW |
| M10 Federated lookups | workgraph /api/lookup/* proxies to IAM + agent-and-tools with user-JWT forwarding; NodeInspector pickers; Agent/Tool snapshot at AGENT_TASK start (externalTemplateId) |
Picker dropdowns populated from real services; snapshot row written on first run, reused afterwards |
| M11.a Service + Contract Registry | New platform-registry :8090 + Postgres :5435; per-service self-register helper (TS + Python); 11 services + 47 capabilities + contracts browsable |
GET :8090/api/v1/services returns 12 (11 production + sample) |
| M11.b Reference Resolver | GET /api/lookup/:entity/:id for 9 kinds; POST /api/lookup/resolve batch (200/207); write-time validation in workflow design POST/PATCH (422 on bad ref) |
bogus refs → 422 with field-level failure; valid → 201 |
| M11.c Snapshot provenance | sourceHash/sourceVersion/fetchedBy on snapshots; new prompt_profile_snapshots + capability_snapshots tables; canonical-JSON sha256 dedupe |
3 runs of same workflow → 1 capability snapshot row |
| M11.d Unified Receipt envelope | cf GET /receipts?trace_id= + workgraph GET /api/receipts?trace_id= joins workgraph + cf + MCP audit |
14 receipts merged from 3 services in chronological order |
| M11.e Event Bus | event_outbox + event_subscriptions + event_deliveries in 5 publishers (IAM Python, workgraph TS, agent-runtime TS, tool-service TS, agent-service TS); Postgres LISTEN/NOTIFY dispatcher with 30s safety sweep, HMAC, 5-attempt retry; workgraph receiver at POST /api/events/incoming; canonical envelope shape across all publishers |
Subscribe to *.created → trigger from any service → workgraph event_log captures with incoming.<event_name> |
| M11 follow-up OTel + Jaeger | Auto-instrumentation in workgraph-api (TS), context-api (Python), tool-service (TS), agent-runtime (TS), agent-service (TS); Jaeger all-in-one in platform-registry compose; W3C traceparent propagated automatically |
Single trace 1cc8ef8ac9a1207b had 59 spans across 4 services. UI: http://localhost:16686 |
| M11 follow-up Service-token auto-mint | IAM POST /api/v1/auth/service-token + workgraph + cf bootstrap + IAM_BOOTSTRAP_USERNAME/PASSWORD env. Replaces 60-min admin-JWT-passing-via-env. |
Both services start with IAM_SERVICE_TOKEN="", mint 30-day tokens on first call |
| M11 follow-up Embedded LLM gateway in MCP | LLM-agnostic provider router inside mcp-server: OpenAI Chat Completions, Anthropic Messages API (with tool-calling translation), GitHub Copilot Headless. Provider keys live in operator's local env. New GET /llm/providers route surfaces what's configured. |
All 4 providers route correctly; missing keys produce clean errors. Set OPENAI_API_KEY / ANTHROPIC_API_KEY / COPILOT_TOKEN in env to use real providers. |
Singularity Portal (:5180)
│
┌─────────────────┼─────────────────┐
▼ ▼ ▼
UserAndCapabillity Agent & Tools Workgraph Studio
(:5175) (:3000) (:5174)
│ │ │
▼ ▼ ▼
IAM Service ┌─prompt-composer (:3004)─┐
(:8100) │ agent-runtime (:3003) │ Workgraph API
│ │ tool-service (:3002) │ (:8080)
│ │ agent-service (:3001) │ │
│ └──────────────────────────┘ │
│ │ │
│ ▼ │
│ Context Fabric (:8000) │
│ llm-gateway (:8001) │
│ memory (:8002) │
│ metrics (:8003) │
│ │
└────────────── shared IAM JWT ──────────────────────┘
- What's in the box
- The five planes
- Quick start
- Service inventory
- Using the platform
- End-to-end demo: workgraph DAG calls composer calls context-fabric
singularity.shcheatsheet- Architecture deep-dive
- Migration history (what was built)
- Troubleshooting
- Open items / roadmap
| App | Role | Stack | Ports |
|---|---|---|---|
| singularity-iam-service | Identity, orgs, teams, roles, capabilities, skills, JWT, MCP server registry, service-token mint, event bus | Python · FastAPI · Postgres | 8100, postgres 5433 |
| agent-and-tools | Agent definitions, tool registry, prompt assembly, agent CRUD UI; per-service event bus + OTel | TypeScript monorepo · Express · Next.js · Prisma · Postgres+pgvector | 3000–3004, postgres 5432 |
| context-fabric | LLM cost optimizer (context compaction + token-saving ledger), /execute orchestrator, /receipts join, OTel |
Python · 4× FastAPI · SQLite | 8000–8003 |
| mcp-server | Per-tenant MCP execution engine + WS bridge + embedded LLM gateway (OpenAI / Anthropic / Copilot Headless / mock). Customer-deployed, holds its own provider keys. | TypeScript · Express · WebSocket | 7100 |
| workgraph-studio | Visual DAG designer + workflow runtime, federated /api/lookup/*, snapshot layer, unified /api/receipts, event bus + receiver, OTel |
React + ReactFlow + Zustand · Express + Prisma · MinIO | 5174 (web) / 8080 (api), postgres 5434, minio 9000-9001 |
| platform-registry | Service + Contract Registry: every service self-registers on startup with capabilities + OpenAPI/event/node contracts | TypeScript · Express · Postgres | 8090, postgres 5435 |
| UserAndCapabillity | Visual admin SPA for IAM | React 19 · Vite · Tailwind · Radix · Zustand | 5175 |
| singularity-portal | The wrapper SPA — single login + dashboard tiles + deep links | React 19 · Vite · Tailwind · Radix | 5180 |
| jaeger (observability) | All-in-one OTel trace UI; receives spans from all instrumented services | docker image | 16686 (UI), 4317/4318 (OTLP) |
11 production services. Each owns its database. capability_id is the join key across them; joins happen at the application layer, never in SQL.
A useful mental model when deciding "which app should this feature live in?" — match the responsibility to its plane.
┌───────────────────────────────────────────────────────────┐
│ CONTROL IAM + UserAndCapabillity + Portal │
│ decides WHO can do WHAT; humans configure here │
└───────────────────────────────────────────────────────────┘
│
┌───────────────────────────▼───────────────────────────────┐
│ ORCHESTRATION workgraph-studio │
│ decides WHEN and IN WHAT ORDER work happens │
└───────────────────────────────────────────────────────────┘
│
┌───────────────────────────▼───────────────────────────────┐
│ COMPOSITION prompt-composer (inside agent-and-tools) │
│ decides WHAT TO SAY to the LLM (layered prompts + │
│ workflow context + tool contracts + artifacts) │
└───────────────────────────────────────────────────────────┘
│
┌───────────────────────────▼───────────────────────────────┐
│ DATA agent-and-tools (registries, memory) │
│ the WHAT of the work (templates, tools, knowledge) │
└───────────────────────────────────────────────────────────┘
│
┌───────────────────────────▼───────────────────────────────┐
│ OPTIMIZATION context-fabric │
│ makes LLM calls cheaper (compaction + cost tracking) │
└───────────────────────────────────────────────────────────┘
- Docker Desktop (or Docker Engine + the Compose v2 plugin)
- 8 GB+ RAM available to Docker
- Free host ports:
3000–3004,5174–5175,5180,5432–5434,8000–8003,8080,8100,9000–9001 - Optional:
OPENROUTER_API_KEYincontext-fabric/.envfor real LLM calls (otherwise usesmockprovider)
cd /path/to/SingularityNeoNew
docker compose up -d # builds + starts 18 containers
docker compose ps # see what's runningThen open http://localhost:5180 and log in with admin@singularity.local / Admin1234!.
To tear down (keeps data):
docker compose downTo wipe data volumes:
docker compose down -vWraps the master compose with friendlier subcommands. Useful for starting/stopping individual services.
./singularity.sh up # start all
./singularity.sh up portal # start just the portal
./singularity.sh status # ps
./singularity.sh urls # color-coded URL cheatsheet
./singularity.sh logs workgraph-api -f # follow logs
./singularity.sh restart prompt-composer
./singularity.sh stop workgraph-web
./singularity.sh down # stop all (keep data)
./singularity.sh nuke # stop + delete data volumes (confirms)
./singularity.sh login # smoke-test IAM /auth/local/login
./singularity.sh ls # list known service names
./singularity.sh build [service] # rebuild image(s)
./singularity.sh help # full usageEach app still has its own docker-compose.yml if you want to run a subset without the master:
cd singularity-iam-service && docker compose up -d
cd context-fabric && docker compose up -d
cd agent-and-tools && docker compose up -d
cd workgraph-studio/infra/docker && docker compose up -d # postgres+minio+api+web
cd UserAndCapabillity && npm install && npm run dev
cd singularity-portal && npm install && npm run devHeads up: the per-app compose files use different container names + ports than the master (e.g.
agentandtools-postgresvs.singularity-at-postgres). Don't mix them — pick one approach and stick with it.
| Service | URL | Auth | Notes |
|---|---|---|---|
| portal | http://localhost:5180 | IAM JWT | the wrapper SPA — start here |
| user-and-capability | http://localhost:5175 | IAM JWT | IAM admin SPA |
| workgraph-web | http://localhost:5174 | workgraph token | Designer + Runtime UI |
| agent-web | http://localhost:3000 | optional JWT | Next.js admin |
| iam-service | http://localhost:8100/api/v1 | bearer (login) | OpenAPI: /docs |
| workgraph-api | http://localhost:8080/api | workgraph token | DAG runtime |
| prompt-composer | http://localhost:3004/api/v1 | optional JWT | /compose-and-respond |
| agent-runtime | http://localhost:3003/api/v1 | optional JWT | agent templates, memory |
| tool-service | http://localhost:3002/api/v1 | optional JWT | tool registry, /tools/discover, /tools/invoke |
| agent-service | http://localhost:3001/api/v1 | optional JWT | agent CRUD |
| context-api | http://localhost:8000 | none | /chat/respond, /docs (OpenAPI) |
| llm-gateway | http://localhost:8001 | none | /llm/respond, /llm/models, /docs |
| context-memory | http://localhost:8002 | none | /memory/messages, /context/compile |
| metrics-ledger | http://localhost:8003 | none | /metrics/dashboard |
| iam-postgres | localhost:5433 | singularity / singularity |
singularity_iam DB |
| at-postgres | localhost:5432 | postgres / singularity |
singularity DB (pgvector) |
| wg-postgres | localhost:5434 | workgraph / workgraph_secret |
workgraph DB |
| wg-minio | http://localhost:9000 (console :9001) | workgraph / workgraph_secret |
artifact storage |
Open http://localhost:5180 → Sign in with admin@singularity.local / Admin1234!. The portal stores the IAM JWT in localStorage under singularity-portal.auth and forwards it to every backend.
The portal home shows four live tiles pulling from each backend:
- My open tasks — counts + recent items from
workgraph /api/runtime/inbox - Workflow runs — active and recent
WorkflowInstances across templates - LLM cost & token savings — total tokens saved + cost saved from
metrics-ledger - Your capabilities — IAM capability list filtered to the signed-in user
The sidebar's Apps section deep-links to:
- Workgraph Designer — the visual DAG editor + runtime UI
- Agent & Tools — agents, prompts, tools admin
- IAM Admin — users, roles, capabilities
The deep-link apps each handle their own auth right now (workgraph-web has its own login). Single-sign-on across all UIs is on the roadmap (set
AUTH_PROVIDER=iamon workgraph + change tile auth in user-and-capability).
This is the wire that ships in the current build: a workflow runs, calls the composer, calls context-fabric, gets a response, persists three correlated audit records.
# 1. Bring up everything
./singularity.sh up
sleep 30 # let migrations + first builds complete
# 2. Login to workgraph-api (its own user, separate from IAM today)
TOKEN=$(curl -sS -X POST http://localhost:8080/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"admin@workgraph.local","password":"admin123"}' | jq -r .token)
# 3. Create a workgraph Agent (workgraph's own registry)
AGENT_ID=$(curl -sS -X POST http://localhost:8080/api/agents \
-H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
-d '{"name":"Demo Agent","provider":"mock","model":"mock-fast","skillIds":[]}' | jq -r .id)
# 4. Find a seeded composer agentTemplateId (from agent-and-tools/agent-runtime seed)
TEMPLATE_ID=$(docker exec singularity-at-postgres psql -U postgres -d singularity \
-tAc 'SELECT id FROM "AgentTemplate" LIMIT 1;' | tr -d ' ')
# 5. Build a 2-node DAG: START → AGENT_TASK → END
WF_ID=$(curl -sS -X POST http://localhost:8080/api/workflow-templates \
-H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
-d '{"name":"E2E Demo"}' | jq -r .id)
START=$(curl -sS -X POST http://localhost:8080/api/workflow-templates/$WF_ID/design/nodes \
-H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
-d '{"nodeType":"START","label":"start","positionX":0,"positionY":0}' | jq -r .id)
AGENT_NODE=$(curl -sS -X POST http://localhost:8080/api/workflow-templates/$WF_ID/design/nodes \
-H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
-d "{
\"nodeType\":\"AGENT_TASK\",\"label\":\"audit\",\"positionX\":200,\"positionY\":0,
\"config\":{
\"agentId\":\"$AGENT_ID\",
\"agentTemplateId\":\"$TEMPLATE_ID\",
\"task\":\"Audit {{instance.vars.module}} for OWASP issues.\",
\"modelOverrides\":{\"provider\":\"mock\",\"model\":\"mock-fast\"},
\"contextPolicy\":{\"optimizationMode\":\"medium\"},
\"toolDiscovery\":{\"enabled\":false}
}
}" | jq -r .id)
END=$(curl -sS -X POST http://localhost:8080/api/workflow-templates/$WF_ID/design/nodes \
-H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
-d '{"nodeType":"END","label":"end","positionX":400,"positionY":0}' | jq -r .id)
curl -sS -X POST http://localhost:8080/api/workflow-templates/$WF_ID/design/edges \
-H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
-d "{\"sourceNodeId\":\"$START\",\"targetNodeId\":\"$AGENT_NODE\",\"edgeType\":\"SEQUENTIAL\"}" > /dev/null
curl -sS -X POST http://localhost:8080/api/workflow-templates/$WF_ID/design/edges \
-H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
-d "{\"sourceNodeId\":\"$AGENT_NODE\",\"targetNodeId\":\"$END\",\"edgeType\":\"SEQUENTIAL\"}" > /dev/null
# 6. Run with a Mustache var
RUN_ID=$(curl -sS -X POST http://localhost:8080/api/workflow-templates/$WF_ID/runs \
-H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
-d '{"name":"demo run","vars":{"module":"auth-service"},"globals":{}}' | jq -r .id)
curl -sS -X POST http://localhost:8080/api/workflow-instances/$RUN_ID/start \
-H "Authorization: Bearer $TOKEN" > /dev/null
sleep 5
# 7. Inspect the result — three correlated IDs
docker exec singularity-wg-postgres psql -U workgraph -d workgraph -tAc "
SELECT 'promptAssemblyId: ' || (\"structuredPayload\"::json->>'promptAssemblyId') ||
E'\nmodelCallId: ' || (\"structuredPayload\"::json->>'modelCallId') ||
E'\ntokens_saved: ' || (\"structuredPayload\"::json->'optimization'->>'tokens_saved')
FROM agent_run_outputs
WHERE \"runId\" IN (SELECT id FROM agent_runs WHERE \"instanceId\" = '$RUN_ID');"
# Inspect the assembled prompt in composer
ASSEMBLY_ID=$(docker exec singularity-wg-postgres psql -U workgraph -d workgraph -tAc \
"SELECT \"structuredPayload\"::json->>'promptAssemblyId' FROM agent_run_outputs \
WHERE \"runId\" IN (SELECT id FROM agent_runs WHERE \"instanceId\" = '$RUN_ID');" | tr -d ' ')
docker exec singularity-at-postgres psql -U postgres -d singularity -tAc \
"SELECT \"contentSnapshot\" FROM \"PromptAssemblyLayer\" \
WHERE \"promptAssemblyId\" = '$ASSEMBLY_ID' AND \"layerType\" = 'TASK_CONTEXT';"
# → "# Current Task\nAudit auth-service for OWASP issues." ✓ var substitutedup [service] start all (or just one)
down stop everything (keeps volumes)
nuke stop + delete all data volumes (confirms)
stop <service> stop one service
restart <service> restart one service
status (ps) list services + state
logs <service> tail logs (pass -f to follow)
build [service] rebuild image(s)
urls printable URL cheatsheet
ls list known service names
login smoke-test IAM /auth/local/login
help usage
prompt-composer (port 3004, in the agent-and-tools workspace) is the new center of gravity. It owns the prompt-assembly tables (PromptProfile, PromptLayer, PromptAssembly) and exposes:
POST /api/v1/prompt-profiles(CRUD)POST /api/v1/prompt-layers(CRUD)POST /api/v1/prompt-assemblies(legacy assemble — same contract as agent-runtime used to expose)POST /api/v1/compose-and-respond(the new value-add)
/compose-and-respond is what workgraph's AgentTaskExecutor calls. It:
- Builds a substitution context from
workflowContext({{instance.vars.x}},{{node.priorOutputs.y}},{{capability.metadata.z}},{{artifacts.<label>.excerpt}},{{task}}) - Loads the template's base profile + binding overlay layers
- Adds capability context, knowledge artifacts, distilled memory layers
- Adds workflow-phase layers
- Builds a
TOOL_CONTRACTlayer from static grants +tool-service /tools/discover, with both JSON Schema and natural-language summary per tool - Renders artifacts as
ARTIFACT_CONTEXTlayers (priority 600) — supports inlinecontent, pre-extractedexcerpt, orminioRef(placeholder; full fetch is M4.1 work) - Adds
TASK_CONTEXT(priority 900) - Appends node-level
EXECUTION_OVERRIDElayers (priority 9999) - Sorts by priority, concatenates, hashes, persists
PromptAssembly+PromptAssemblyLayerrows - Calls
context-fabric /chat/respondwith the assembledsystem_promptandtaskasmessage - Returns a unified response with three correlation IDs:
promptAssemblyId,modelCallId,contextPackageId
apps/api/src/modules/workflow/runtime/executors/AgentTaskExecutor.ts does the M5 plumbing:
- Reads
node.configforagentTemplateId,task, optionalartifacts/overrides/modelOverrides/contextPolicy - Reads
instance.context._varsandinstance.context._globals - Walks prior
AgentRunoutputs to populatepriorOutputs - POSTs to
prompt-composer /compose-and-respond - Persists the response on
AgentRunOutput.structuredPayloadwith full correlation - Sets
AgentRun.status = AWAITING_REVIEWon success,FAILEDon composer error - Emits
AgentRunStarted/AgentRunCompleted/AgentRunFailedoutbox events
The full architecture history (5+ rounds of decisions, gap analysis, target topology, phased rollout) lives at ~/.claude/plans/in-the-singularityneonew-i-agile-fern.md.
The composition plane was added in five milestones, each verified end-to-end:
| Milestone | What it delivered |
|---|---|
| M1 | Scaffolded apps/prompt-composer + relocated PromptProfile/PromptLayer/PromptAssembly/PromptAssemblyLayer Prisma models |
| M2 | Mirrored prompt CRUD endpoints at parity (profiles, layers, attach, assemblies). Smoke-tested via curl against shared Postgres. |
| M3 | Cut over the agent-and-tools admin (web/) to call composer instead of agent-runtime: new /api/composer/* Next.js rewrite, web/Dockerfile gained PROMPT_COMPOSER_URL build-arg (Next.js bakes rewrites at build time, not runtime), agent-runtime's prompt route mount removed |
| M4 | Added POST /compose-and-respond — workflow context, artifacts, Mustache substitution, tool-service discovery, context-fabric call, three correlation IDs |
| M5 | Wired workgraph's AgentTaskExecutor to call /compose-and-respond. Fixed AgentRun.startedAt/completedAt lifecycle. Fixed workgraph Dockerfile (workspace context + Alpine OpenSSL) so it actually builds and runs in production. |
If you have a Homebrew Postgres running on localhost:5432 it'll shadow Docker's at-postgres. Symptom: clients connecting to localhost:5432 from the host get the Homebrew DB (no Singularity tables) while clients inside the Docker network see the right one. Fix:
brew services stop postgresql@14 # or whatever version(Or remap the host port for at-postgres in the master compose to e.g. 5435:5432.)
The IAM Postgres binds 5433. The master compose remaps workgraph postgres to 5434 to avoid this. If you also run the original workgraph-studio/infra/docker/docker-compose.yml, edit its host port from 5433:5432 to 5434:5432.
Workgraph runs AUTH_PROVIDER=local by default — it has its own user table and login endpoint. The portal sends the IAM JWT to all backends; workgraph 401s on it. The portal handles this gracefully (only the IAM client is "authoritative" for session — workgraph 401s show as a tile error, the user stays signed in).
To make workgraph honor IAM JWTs, set on workgraph-api:
environment:
AUTH_PROVIDER: iam
IAM_BASE_URL: http://iam-service:8100/api/v1
IAM_SERVICE_TOKEN: <a long-lived token from IAM>Workgraph already ships an iam/client.ts that handles this — just hasn't been turned on.
Some images are missing libssl. Fix in the affected Dockerfile:
RUN apk add --no-cache opensslThe master compose runs the portal in nginx on :5180. If you also npm run dev it locally, kill one or the other.
lsof -i :5180 # find the PID
kill <pid>Compose only re-reads env on container creation. Use:
./singularity.sh restart <service> # picks up env from docker-compose.yml
docker compose up -d <service> # sameFor portal/web env changes (Vite bakes some at build time), rebuild:
./singularity.sh build <service>
./singularity.sh restart <service>./singularity.sh down # stop, keep data
./singularity.sh nuke # stop + delete all volumes (ASKS first)
docker compose down -v --remove-orphans # raw equivalentThese are real gaps, not nice-to-haves:
- SSO — only IAM is authoritative for the portal. Workgraph-api needs
AUTH_PROVIDER=iamflipped on so the same IAM JWT works platform-wide. - Workgraph Agent table vs. agent-and-tools
AgentTemplate— two parallel registries today. The "mirror / federate / collapse" decision is unresolved. - AgentRun correlation columns —
promptAssemblyId,modelCallId,contextPackageIdare stuffed instructuredPayloadJSON. Promote to dedicated columns for queryability. - Streaming — composer + workgraph still synchronous on
/chat/respond. No SSE token streaming yet. - MCP bridge — slated to live inside context-fabric on
:8004; not built. Needs a per-capability MCP server registry in IAM (mcp_servers,mcp_server_secrets,mcp_capability_bindings). - Knowledge / learning pipeline —
CapabilityCodeSymbol/CapabilityCodeEmbedding/CapabilityKnowledgeArtifactschemas exist in agent-runtime; no symbol extractor or embedder service populates them. Thelearning_candidates → learning_profiles → DistilledMemorypipeline lacks a distillation worker + promotion UI. - Per-tenant multi-tenancy — capability scoping is the soft boundary; no schema-per-tenant or hard isolation yet.
- Observability — no OpenTelemetry/Jaeger across the stack; correlation IDs exist but aren't wired into traces.
- Production deploy — the Dockerfiles work; CI/CD configs (
.github/workflows) do not exist yet.
The Singularity wordmark and the silver swirl mark are shared assets used by every UI in the platform. They live in one place:
branding/
├── README.md brand guide: colors, typography, placement rules
├── tokens.css CSS custom properties (--brand-forest, --brand-green, …)
├── singularity-logo.png full lockup (drop the official PNG here)
└── singularity-mark.png swirl-only — favicons + tight headers (optional)
# 1. Save the official logo PNG at:
# branding/singularity-logo.png (full lockup)
# branding/singularity-mark.png (swirl-only — optional; falls back to logo)
# 2. Sync to every app's public/ directory:
./bin/sync-branding.sh
# Output:
# ✓ synced → singularity-portal/public/
# ✓ synced → UserAndCapabillity/public/
# ✓ synced → agent-and-tools/web/public/
# ✓ synced → workgraph-studio/apps/web/public/
# ✓ 4 app(s) updated.Re-run any time the canonical files change. Each app loads /singularity-mark.png (with /singularity-logo.png as a graceful fallback) from its own public/ root, so cross-domain CORS is never a concern.
| Element | Value |
|---|---|
| Sidebar background | forest gradient #0E3B2D → #082821 |
| Wordmark | "Singularity" — Inter 700, tracking +0.04em, warm white #F5F2EA |
| Tagline | "GOVERNED AGENTIC DELIVERY" — Inter 600, tracking +0.18em, ~55% opacity |
| Primary action | green #00843D (hover #006236) |
| Active nav | green left/right border #00A651 + 8% white background |
| Tab title | every app: <title>Singularity — <App Name></title> |
| Favicon | /favicon.png (synced from branding/singularity-mark.png) |
See branding/README.md for the full guide (typography rules, placement, prohibited usage).
SingularityNeoNew/
├── docker-compose.yml # master compose (18 services)
├── singularity.sh # CLI wrapper
├── README.md # this file
├── branding/ # canonical logo + tokens.css (one drop, all apps consume)
├── bin/
│ └── sync-branding.sh # copies branding/*.png into each app's public/
│
├── singularity-iam-service/ # Python FastAPI — identity
├── agent-and-tools/ # TS monorepo — agents/tools/composer/runtime
│ ├── apps/
│ │ ├── agent-service/
│ │ ├── tool-service/
│ │ ├── agent-runtime/
│ │ └── prompt-composer/ # NEW (composition plane)
│ ├── packages/
│ ├── web/ # Next.js admin
│ └── docker-compose.yml # per-app compose (alternative to master)
├── context-fabric/ # Python — 4× FastAPI for LLM optimization
├── workgraph-studio/ # TS pnpm workspace — DAG designer + runtime
│ ├── apps/api/ # Express + Prisma
│ ├── apps/web/ # React + ReactFlow
│ ├── packages/{shared-types,engine}/
│ └── infra/docker/docker-compose.yml
├── UserAndCapabillity/ # React + Vite — IAM admin SPA
└── singularity-portal/ # NEW — wrapper SPA (this is the front door)
Internal Singularity Neo platform. See per-app READMEs for component-level details: