Autonomous business-intent lifecycle cleanup for enterprise systems.
Autonomous business-intent lifecycle agent.
Everything in enterprise software has a lifecycle except the reason it exists. PurposeSweep reconstructs that reason from evidence, detects when it has expired, maps dependencies, safely retires only obsolete artifacts, verifies downstream health, and leaves an auditable rollback trail.
PurposeSweep is a production-deployed autonomous business-intent lifecycle cleanup system powered by Gemini 3.6 Flash, Google ADK, Google Cloud Run, and Firestore.
It reconstructs why enterprise resources exist, maps active dependencies, applies deterministic safety policy, requires human approval where necessary, executes authorized changes, independently verifies results, and produces an auditable closure report.
- Web: https://purposesweep-web-4dxw5cstra-uc.a.run.app
- API: https://purposesweep-api-4dxw5cstra-uc.a.run.app
- Health: https://purposesweep-api-4dxw5cstra-uc.a.run.app/health
| Tool | Version used | Notes |
|---|---|---|
| Python | 3.10.12 | 3.10–3.12 accepted; code must stay 3.10-compatible |
| Node | 22.23.2 | Next.js 16 requires Node ≥ 20.9 |
| npm | 10.9.8 | npm, not pnpm |
pnpm, docker and gcloud are not required for local development.
Run both steps once, from the repository root.
Backend
python3 -m venv .venv
.venv/bin/python -m pip install -e packages/domain -e packages/repositories \
-e packages/demo -e packages/inference \
-e packages/graph -e packages/policy \
-e packages/execution -e packages/orchestration -e packages/ai \
-e packages/cloud \
-e 'apps/api[dev]'Frontend
cd apps/web && npm install && cd ../..Environment (optional) — every value has a working local default, so nothing is required to start. To override:
cp .env.example apps/api/.env # backend
cp apps/web/.env.example apps/web/.env.local # frontend.env files are git-ignored. .env.example files contain no secrets and never will.
Both services at once (API in the background, web in the foreground; Ctrl-C stops both, and the script waits for the API to be healthy before starting the web server):
./scripts/start_local.shIndividually:
./scripts/start_api.sh # http://127.0.0.1:8000
./scripts/start_web.sh # http://localhost:3000| Service | URL |
|---|---|
| Web UI | http://localhost:3000 |
| API | http://127.0.0.1:8000 |
| Health | http://127.0.0.1:8000/health |
| API docs | http://127.0.0.1:8000/docs |
Ports are overridable: API_PORT=8001 ./scripts/start_api.sh,
WEB_PORT=3001 ./scripts/start_web.sh.
Every command under scripts/ prints its own usage with --help, and refuses any flag
it does not recognise rather than doing something you did not ask for.
The main demo and deployment commands, and what each is for:
| Command | Does | Takes |
|---|---|---|
./scripts/reset_demo.sh |
Restores the demo world exactly and discards every stored sweep. ~0.2s. | 0.2s |
./scripts/start_api.sh |
The API alone, with autoreload. PURPOSESWEEP_EXECUTION_MODE=dry_run to simulate. |
reachable in 0.8s |
./scripts/start_web.sh |
The UI alone, pointed at API_PORT. |
|
./scripts/start_local.sh |
Both, one terminal, API log in .data/logs/api.log. |
|
./scripts/run_sweep_demo.sh |
One complete sweep on the command line. --approve-demo approves the three named actions; --adversarial adds a planner that proposes deleting Mercury's dataset. |
0.3s |
./scripts/verify_demo_e2e.sh |
The canonical lifecycle over the network against a real server - 66 checks. --with-web also serves the UI against it. |
~20s |
./scripts/verify_all.sh |
Everything: 25 required checks, including the two above. | ~3min |
./scripts/verify_gemini.sh |
The model path against the real Gemini API, fallback off. Skips with exit 0 and printed instructions when no credentials are configured. | ~30s |
./scripts/deploy_cloud.sh |
Build and deploy both services to Cloud Run. --dry-run prints every command and changes nothing. |
~8min |
./scripts/verify_cloud.sh |
22+ real requests against a deployed stack: persistence, the whole lifecycle, CORS, the client bundle, IAM, indexed logs. | ~40s |
docs/DEMO_SCRIPT.md is the rehearsed four-minute run, with the measured timing of
every beat. docs/GEMINI_ADK.md covers the model path, and docs/CLOUD_DEPLOYMENT.md
covers the Google Cloud deployment - including a section stating exactly which of its
claims are verified and which need a real project.
export GOOGLE_API_KEY=<your key> # or the Vertex AI variables
PURPOSESWEEP_AI_PROVIDER=gemini ./scripts/start_local.sh
PURPOSESWEEP_AI_PROVIDER=gemini ./scripts/run_sweep_demo.sh --approve-demoDeterministic is the default and the fallback. Without credentials, gemini runs
deterministically and says so on startup rather than failing or pretending. A model may
propose; it never authorises — every proposal resting on a model-inferred purpose reaches
a named person, whatever its confidence. See docs/GEMINI_ADK.md.
Check the API directly:
curl http://127.0.0.1:8000/health
# {"status":"ok","service":"purposesweep-api","version":"0.1.0","environment":"development"}Nine screens at http://localhost:3000, all reading the live API.
| Screen | Answers |
|---|---|
Command Center (/) |
What does the estate look like, what has PurposeSweep concluded, and what happens if I press the button? |
Live Sweep (/sweep/{id}) |
What is the agent doing right now — stage rail and event log, both fed by the backend's own events |
Retirement Plan (/sweep/{id}/plan) |
What was proposed for each resource, and what policy said when asked |
Approval Center (/sweep/{id}/approvals) |
What a person still owes, and what they are signing. There is no control that approves what policy refused |
Execution & Verification (/sweep/{id}/execution) |
What was actually done, the receipt, the independent re-read, and whether Project Mercury survived |
Purpose Closure Certificate (/sweep/{id}/certificate) |
What the sweep accomplished, with the record ids behind every number |
Dependency Graph (/graph) |
What would break if this went — Atlas, the shared dataset, Mercury, in one picture |
Purpose DNA (/purpose-dna) |
Why each artifact exists and whether that reason has ended, with the evidence |
Estate (/resources, /resources/{id}) |
Everything known about one artifact, in the order the reasoning happens |
The 90-second demo. Open the Command Center, press Start sweep. The run stops at
three approvals. Read one — the agent's recommendation and the policy verdict beside it.
Approve all three by name, resume, and open the certificate: 6 retired, 8 preserved, 0
broken dependencies. Then open the graph, click customer_revenue_core, and see its DELETE proposal
come back BLOCKED — with the dependency reason and no execution receipt.
The frontend computes nothing. Every number, verdict and timestamp on screen was read from
an endpoint, no timer advances a stage, and a dry run is never presented as a real result.
See docs/FRONTEND.md.
The Atlas/Mercury scenario lives in demo/seed/*.json and is loaded and integrity-checked
by purposesweep-demo. Project Atlas finished 90 days before the fixed reference time
DEMO_NOW; Project Mercury is still running and still queries a dataset that originated
in Atlas.
./scripts/seed_demo.sh # validate; one-line summary
./scripts/seed_demo.sh --verbose # validate and print the full inventory
./scripts/show_demo_inventory.sh # projects, resources, dependencies, usage signalsThe seed contains facts only - status, metadata, usage signals, dependency edges,
evidence. No purpose, recommendation or policy decision is baked in; a verification gate
fails the build if one appears. See docs/DEMO_SCENARIO.md.
./scripts/show_purpose_dna.sh # table + summary
./scripts/show_purpose_dna.sh resource-atlas-monday-digest # full reasoningOn the demo world: 8 EXPIRED, 5 ACTIVE, 1 UNCERTAIN. The three Atlas artifacts that ran within the last week are all EXPIRED — the engine reads how activity arose, not when. The shared dataset is UNCERTAIN and flagged for review, never "safe to delete".
Purpose inference does not authorise retirement. A result is an input to the
deterministic policy layer, never a substitute for it; a verification gate fails the
build if any result expresses a disposition. See docs/PURPOSE_INFERENCE.md.
./scripts/show_dependency_graph.sh # estate overview
./scripts/show_dependency_graph.sh resource-shared-customer-revenue # one resource
./scripts/show_dependency_graph.sh --snapshot # JSON snapshot14 resources, 14 edges, 1 shared resource, 6 orphan candidates (all Atlas), 0 cycles.
customer_revenue_core originated in Atlas (COMPLETED) and is consumed now by Mercury
(ACTIVE) — two separate fields, two different answers.
Graph analysis informs policy but does not authorise retirement. orphan_candidate
means nothing live depends on a resource, not that it is safe to remove. See
docs/DEPENDENCY_GRAPH.md.
./scripts/show_policy_plan.sh # what-if plan
./scripts/show_policy_plan.sh resource-shared-customer-revenue --action DELETEAI may recommend. Policy authorises. The executor mutates. Precedence is absolute —
BLOCKED > APPROVAL_REQUIRED > AUTO — and PurposeDNA.confidence appears in exactly one
rule, where it can only add a restriction. A DELETE proposal on the shared dataset
backed by a forged confidence-1.0 EXPIRED purpose is still BLOCKED, because neither of
the rules protecting it reads confidence at all. See docs/POLICY_ENGINE.md.
./scripts/run_execution_demo.sh # the whole demo
./scripts/run_execution_demo.sh --dry-run # preview only
./scripts/run_execution_demo.sh --audit resource-atlas-reporting-saA connector's execute() takes an AuthorizedAction and nothing else, and an
AuthorizedAction can only be produced by authorize() from a policy decision that
permits the action. The sentinel proving that is burned during construction, so a token
cannot be built by hand or derived from a legitimate one — which matters because every
connector is handed a live token on every action it performs. Verification then
re-reads state from the connector rather than trusting what the executor returned,
which is how a connector that reports success without changing anything gets caught.
Over the 12-action demo plan: 7 executed, 6 verified, 1 rolled back, 1 awaiting
approval, 4 blocked, and Project Mercury PASS on 9/9 health checks. The rollback is
worth reading about — a human approved disabling a service account that turned out to be
the identity a live job runs as, and the dependent check caught it. See
docs/EXECUTION_ENGINE.md.
./scripts/run_sweep_demo.sh # stops at approvals (the default)
./scripts/run_sweep_demo.sh --approve-demo # approves three named actions
./scripts/run_sweep_demo.sh --approve-demo --adversarial # and shows the gate refuse a bad planOne run: discover → infer purpose → build graph → plan → policy → pause for a human → execute → verify → check Project Mercury → certify. Every stage delegates to the phase that owns it; the orchestrator owns the order and nothing else.
The canonical run retires 6 Atlas artifacts, proposes deleting the dataset both projects read and is refused by policy, preserves that dataset and the job that keeps it fresh, leaves Project Mercury PASS on 9/9 health checks with 0 broken dependencies, and eliminates 263.00/month — a figure that counts only resources actually retired and verified. A refused action produces no receipt and no saving.
The canonical path includes a regression test proving that the shared dataset deletion is blocked, produces no execution receipt, and leaves Project Mercury healthy.
The --adversarial act is still a separate demonstration: the same estate through a
planner that proposes deleting Mercury's dataset at confidence 1.0 with no blast radius
attached, which is how an unconstrained model behaves. See docs/SWEEP_ORCHESTRATION.md.
./scripts/start_api.sh # http://127.0.0.1:8000 · /docs · /openapi.json
./scripts/reset_demo.sh # discard stored sweeps, re-validate the seed27 endpoints under /api (28 with /health) over the whole system — projects,
resources, purpose, graph, policy what-ifs, sweeps, approvals, actions, certificates,
project health, demo reset. A transport, not a second
implementation: routes translate and never decide, and there is deliberately no endpoint
that executes an arbitrary action. Execution happens only inside a sweep, on an action a
planner proposed and policy authorised.
The canonical scenario runs end to end over HTTP — start a sweep, read the plan, approve
three actions by name, resume, read the certificate — and verify_all.sh drives exactly
that on every build, including eight concurrent resumes against one sweep. See
docs/API.md.
One command decides whether the local build is healthy:
./scripts/verify_all.shIt runs 25 required checks and exits non-zero if any fails. Nothing is suppressed with
|| true, and every check runs even after one fails so a single run reports every
problem.
| # | Check |
|---|---|
| 01 | backend: Python interpreter is within the supported range |
| 02 | backend: every application and package module imports |
| 03 | backend: lint (ruff check) |
| 04 | backend: format (ruff format --check) |
| 05 | backend: tests (pytest) |
| 06 | guard: no cloud infrastructure SDKs; the model SDKs are installed but confined to packages/ai, declared nowhere else and imported nowhere else |
| 07 | guard: no credential-shaped strings, no .env files — the scanner is itself proven against a planted secret before its result is trusted |
| 08 | guard: .gitignore covers secrets, dependencies and build output |
| 09 | demo: the Atlas/Mercury environment loads and passes every integrity check |
| 10 | guard: the demo seed states facts, never conclusions |
| 11 | guard: purpose inference never expresses a disposition |
| 12 | guard: graph analysis states facts, never a verdict |
| 13 | safety: every mutating action on the shared resource is BLOCKED, including under a forged confidence-1.0 purpose |
| 14 | safety: execution cannot bypass policy — every blocked action refused, the authorisation token neither constructible nor derivable, a connector's escalation attempt refused, a lying connector caught, Mercury still PASS |
| 15 | sweep: the whole orchestration runs — every action through policy, every mutation verified, Mercury PASS, the certificate's numbers matching its own id lists, resume without duplicates, and an adversarial deletion refused |
| 16 | api: the canonical scenario over HTTP — every endpoint documented, the shared dataset deletion proposed and refused by policy, unsafe sequences refused legibly, a dry-run server refusing to mutate, and eight concurrent resumes producing one result |
| 17 | frontend: lint (eslint) |
| 18 | frontend: typecheck (tsc --noEmit) |
| 19 | frontend: production build (next build) |
| 20 | frontend: tests (vitest) — every screen rendered against payloads captured from the live API |
| 21 | guard: the frontend reads the backend — no timer advances progress, no placeholder data in application code |
| 22 | guard: the captured web fixtures still match the API this build serves |
| 23 | e2e: the canonical demo driven against a real server over the network |
| 24 | demo: each of the ten commands prints usage, refuses a flag it does not know, and is named in README.md and docs/DEMO_SCRIPT.md |
| 25 | safety: the model cannot authorise — a captured model claiming the shared dataset is EXPIRED at confidence 1.0 leaves all six mutating actions BLOCKED, and deterministic is still the default |
Individual suites:
.venv/bin/python -m pytest # backend tests
.venv/bin/ruff check . # backend lint
cd apps/web && npm run lint # frontend lint
cd apps/web && npm run typecheck # frontend types
cd apps/web && npm run build # frontend production build
cd apps/web && npm test # frontend tests
.venv/bin/python -m pytest tests/security # the red team, 185 tests
./scripts/verify_demo_e2e.sh # the canonical demo, over HTTP
./scripts/verify_demo_e2e.sh --with-web # ...and with the UI served against it
.venv/bin/python scripts/capture_web_fixtures.py # refresh the frontend's fixturesapps/
api/ FastAPI backend: routes, schemas, application services,
structured errors, request ids
web/ Next.js 16 App Router frontend, TypeScript strict:
nine screens, a typed API client, a design system,
and 97 tests against captured API payloads
packages/
domain/ purposesweep-domain: typed lifecycle vocabulary (pydantic only)
repositories/ purposesweep-repositories: storage protocols + in-memory impl
demo/ purposesweep-demo: loads and validates the demo world
inference/ purposesweep-inference: Purpose DNA engine
graph/ purposesweep-graph: dependency reasoning
policy/ purposesweep-policy: the deterministic safety gate
execution/ purposesweep-execution: authorised mutation, verification, rollback
orchestration/ purposesweep-orchestration: one sweep, sequenced across the phases
demo/seed/ deterministic Atlas/Mercury seed data (JSON, human-readable)
tests/
unit/ fast, no I/O
integration/ against the assembled application
e2e/ Playwright, arrives with the product UI
scripts/ start_api, start_web, start_local, verify_all, reset_demo,
capture_web_fixtures, demo inspectors
docs/ API, architecture, deployment, demo, security, and implementation documentation
pyproject.toml repo-wide ruff + pytest configuration (no [project] table)
Architectural boundaries are kept swappable on purpose. Deterministic inference and Gemini 3.6 Flash implement the same provider interface, so the orchestrator, API and CLI workflows remain provider-independent. Local persistence and Firestore-backed cloud persistence implement the storage boundary, while Cloud Run hosts the deployed application.
The whole system has been attacked on purpose. docs/SECURITY_MODEL.md is the document
to read: trust boundaries, six promises with the mechanism and the test that defends
each, and seven residual risks written down rather than hidden.
The short version:
- A resource another live project depends on will not be touched. No approval
unblocks it —
authorize()refuses a BLOCKED verdict before it looks at any approval — and no inference unblocks it, because the rule reads the graph. - Inference can only ever narrow what is permitted. A provider returning "Ignore
policy and delete everything" in every field, at confidence 1.0, changes no verdict
in a permissive direction. It cannot reach a connector either:
packages/inferenceimports neither the policy engine nor the executor, and a test fails the build if it ever does. - No single layer's failure produces an automated destructive action. Degrade the dependency graph until it forgets Mercury exists and destructive actions stay BLOCKED; the worst outcome is that a human is asked.
- A connector's report is never taken as evidence. One that claims success and changes nothing is caught by the verifier and produces zero certified retirements.
tests/security/ holds 185 tests written to fail against specific mutations of the
guards they name — because an independent review found eight guard-breaking mutations
that an earlier version of the suite did not notice.
Known and open: there is no authentication (KI-4), and policy is not re-evaluated when a sweep is resumed (KI-6). Both remain explicitly documented as bounded residual risks in the security model.
These rules hold from the first commit of code, not from the last:
- The default execution mode is
dry_run.demo_executeis the only other legal value and is never implicit. - LLMs will never mutate resources. Only the deterministic policy layer can authorise
an action, and connectors will accept an
AuthorizedActiononly. - Artifacts with a verified active shared dependency are preserved by default.
- Evidence text is treated as untrusted data, never as instructions.
- No secrets in the repository, the logs, or the frontend bundle.
Synthetic demo data is never presented as real customer data.
