From b2801a04f1546c9ca91fb74b558677b1b1d85f8e Mon Sep 17 00:00:00 2001 From: arpan Date: Tue, 15 Sep 2026 05:49:46 +0530 Subject: [PATCH] ctrlrun.dev becomes a technical site, with one form on it No pricing, no tiers, no sales path. The home page's commercial section is gone, `/protect-my-agent` and `/risk-check` are deleted, and the navbar's "Free, Pro, Enterprise" link and "Protect my agent" button go with them. The primary call to action is now the quickstart, which is where a developer who believes the page actually goes. **One thing is asked for, and it asks for one field.** A section at the end says something is coming that makes controlling agents easy for a business, and takes an address. No company, no "what do your agents do", no qualification: asking a stranger to describe their deployment before they have run `pip install` is a lead form wearing a mailing list's clothes. It posts `intent: 'launch-updates'` to the endpoint the site already uses, which decides the subject and the required fields from the intent and trusts nothing the browser sends. `company` was unconditionally required there, so the new intent needed it made conditional -- and a test pins that the two commercial intents still require it, because a change that adds one intent must not quietly loosen validation for the others. The browser check loses the two multi-step walkthroughs for the deleted pages and gains one for the signup: it asserts the posted intent, that the address arrives, and that no commercial copy is left on the home page. Deleted rather than redirected to a stub, as decided: `/protect-my-agent` and `/risk-check` now redirect to `/` so existing inbound links land somewhere real instead of a 404. Readiness is re-measured down, 6,232 to 6,220, because deleting two pages removed twelve document tests. That number is a measurement, so it moves in whichever direction the suite moves. **Known and deliberate:** the CSS rules for the removed components are still in `style.css`. Several are shared with `execution-boundary.mdx` and with the new form, and unpicking 41 rules by hand hours before a launch is a worse trade than some dead CSS nobody can see. Worth a follow-up. Signed-off-by: arpan --- README.md | 7 +- SEO.md | 2 - STYLE.md | 2 +- assets/verify-website.cjs | 62 +++++------------ docs.json | 18 +++-- docs.mdx | 2 +- docs/production/index.mdx | 2 +- generated/readiness.full.mdx | 2 +- generated/readiness.json | 2 +- generated/readiness.mdx | 2 +- generated/readiness.readme.md | 2 +- index.mdx | 43 +++--------- protect-my-agent.mdx | 40 ----------- risk-check.mdx | 10 --- scripts/render-how-diagram.py | 2 +- snippets/commercial-tiers.jsx | 117 -------------------------------- snippets/execution-boundary.jsx | 2 +- snippets/home-slides.jsx | 2 +- snippets/launch-updates.jsx | 68 +++++++++++++++++++ snippets/risk-check.jsx | 51 -------------- style.css | 16 +++++ website-form/api/interest.mjs | 15 ++-- website-form/interest.test.mjs | 47 +++++++++++++ 23 files changed, 192 insertions(+), 324 deletions(-) delete mode 100644 protect-my-agent.mdx delete mode 100644 risk-check.mdx delete mode 100644 snippets/commercial-tiers.jsx create mode 100644 snippets/launch-updates.jsx delete mode 100644 snippets/risk-check.jsx diff --git a/README.md b/README.md index c541276..45e9c8c 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ This repository is the Mintlify site root, so a file's path here is the URL it s | Path | What it is | |---|---| | `docs.json` | Site configuration and the whole navigation tree | -| `index.mdx`, `execution-boundary.mdx`, `risk-check.mdx`, `protect-my-agent.mdx` | The top-level pages | +| `index.mdx`, `execution-boundary.mdx` | The top-level pages | | `docs/` | Every documentation page: guides, concepts, cookbook, reference, architecture | | `snippets/`, `images/`, `style.css`, `*.js` | Components, media and the browser demos | | `capabilities.yaml` | The capability matrix's source. Edit the YAML, never a rendered table | @@ -88,7 +88,8 @@ Apache-2.0, the same as the library. See [LICENSE](LICENSE). action, the five checks and the refusal each one raises, so the prose beside it stays short. - `docs.mdx` serves `/docs`: the technical overview. - `docs/` is every technical page, published under `/docs/...`. -- `risk-check.mdx` and `protect-my-agent.mdx` are custom-mode product pages. +- `execution-boundary.mdx` is a custom-mode page. The two commercial pages it sat beside, + `risk-check.mdx` and `protect-my-agent.mdx`, were removed when the site became technical only. - `snippets/` holds small client-side React components. Mintlify injects React hooks; do not add cross-snippet imports or third-party browser dependencies. - `style.css` scopes product styling to `.cr-site`; documentation keeps the native layout. @@ -143,7 +144,7 @@ fallback stays available, and a browser retry reuses its request id, so success once the server confirms Resend accepted the message. `website-events.js` and the components emit `ctrlrun:conversion` events for page visits, CTA -clicks, selections, scenario outcomes, risk-check completion and review submission. No +clicks, selections, scenario outcomes and the launch-updates signup. No analytics provider is configured: these are integration hooks and not stored analytics. Event payloads exclude contact details and free-text form contents. diff --git a/SEO.md b/SEO.md index f3e5c78..c5f2690 100644 --- a/SEO.md +++ b/SEO.md @@ -153,5 +153,3 @@ not needed; the assistants that do read `llms.txt` get Mintlify's. |---|---| | `index` | stop wrong, restricted, or malicious AI agent actions · control AI agents running in your platform · works with agents you can and can't modify · any AI agent whatsapp slack claude code cursor codex chatgpt | | `execution-boundary` | what stops an agent action · AI agent execution boundary · how an agent execution boundary is adopted | -| `risk-check` | AI agent execution risk assessment | -| `protect-my-agent` | AI agent execution boundary, connection coverage and ctrlrun Pro/Enterprise governance for businesses | diff --git a/STYLE.md b/STYLE.md index 232d114..72334e7 100644 --- a/STYLE.md +++ b/STYLE.md @@ -103,4 +103,4 @@ python tools/docs_audit/render_capabilities.py --check ## Product experience -The `/`, `/risk-check`, and `/protect-my-agent` pages use Mintlify custom mode. The product brief governs their concise copy and layout; the documentation-only Next section, fixed-copy, and three-domain-list rules do not apply to these pages. Technical pages under `/docs` retain the rules above. +The `/` and `/execution-boundary` pages use Mintlify custom mode. The product brief governs their concise copy and layout; the documentation-only Next section, fixed-copy, and three-domain-list rules do not apply to these pages. Technical pages under `/docs` retain the rules above. diff --git a/assets/verify-website.cjs b/assets/verify-website.cjs index 919f0f2..27bea12 100644 --- a/assets/verify-website.cjs +++ b/assets/verify-website.cjs @@ -58,52 +58,24 @@ module.exports = async function verifyWebsite(page, base = 'http://localhost:300 } await page.setViewportSize({ width: 1280, height: 900 }); - await page.goto(base + '/risk-check'); - await page.getByLabel('Move money', { exact: true }).check(); - for (let index = 0; index < 5; index++) await page.locator('input[name="risk-' + index + '"][value="' + ([2, 4].includes(index) ? 'No' : 'Yes') + '"]').check(); - await page.getByRole('button', { name: 'Check my execution risk →' }).click(); - assert((await page.locator('.cr-risk-result').innerText()).includes('5 execution-risk patterns'), 'All five indicated patterns appear in the risk result'); - assert((await page.locator('.cr-risk-result').innerText()).includes('Execution risk: High'), 'High result has a transparent threshold'); - await page.locator('input[name="risk-0"][value="Unsure"]').check(); - assert(await page.locator('.cr-risk-result').count() === 0, 'Changing an answer clears the stale result'); - for (let index = 0; index < 5; index++) await page.locator('input[name="risk-' + index + '"][value="' + ([2, 4].includes(index) ? 'Yes' : 'No') + '"]').check(); - await page.getByRole('button', { name: 'Check my execution risk →' }).click(); - assert((await page.locator('.cr-risk-result').innerText()).includes('Lower indicated risk'), 'Controls present produce a lower indicated result'); - await page.setViewportSize({ width: 375, height: 812 }); - assert(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), 'Risk check fits mobile'); - await page.goto(base + '/protect-my-agent?domain=DevOps&risk=High&patterns=4&unknowns=1'); - assert((await page.locator('.cr-domain-context').innerText()).includes('DevOps'), 'Domain context carries into the review form'); - await page.getByRole('button', { name: 'Review my request →' }).click(); - assert(await page.locator('.cr-email-preview').count() === 0, 'Empty form cannot prepare a request'); - await page.getByLabel('Work email', { exact: true }).fill('engineer@example.com'); - await page.getByLabel('Company', { exact: true }).fill('Example test company'); - await page.getByLabel('What does your agent do?').fill('Test deployment workflow'); - await page.getByLabel('Which actions can it execute?').fill('Deploy production releases'); - await page.getByLabel('Retry safety', { exact: true }).check(); - await page.getByRole('button', { name: 'Review my request →' }).click(); - const href = await page.getByRole('link', { name: 'Use my email app instead ↗' }).getAttribute('href'); - assert(href.startsWith('mailto:contact@arpanghoshal.com?'), 'Review handoff uses the approved recipient'); - const body = decodeURIComponent(href.split('&body=')[1]); - assert(body.includes('Example test company') && body.includes('Deploy production releases') && body.includes('Retry safety') && body.includes('High'), 'Email brief includes qualification and risk context'); - assert((await page.locator('.cr-email-preview').innerText()).includes('has not been sent'), 'The form never falsely claims delivery'); - assert(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), 'Review form fits mobile'); - await page.getByLabel('Company', { exact: true }).fill('Updated test company'); - assert(await page.locator('.cr-email-preview').count() === 0, 'Editing the brief clears the prepared handoff'); - await page.getByRole('button', { name: 'Review my request →' }).click(); - const submissions = []; - await page.route('https://ctrlrun-review-form.vercel.app/api/review', async route => { - submissions.push(route.request().postDataJSON()); - await route.fulfill({ status: submissions.length === 1 ? 502 : 200, contentType: 'application/json', headers: { 'Access-Control-Allow-Origin': '*' }, body: JSON.stringify(submissions.length === 1 ? { error: 'Provider temporarily unavailable.' } : { ok: true, id: 'mock-only-no-email-sent' }) }); + // The home page's one form. `/risk-check` and `/protect-my-agent` were checked here until + // v0.12; both pages were removed when the site became technical only, and the walkthroughs + // that drove their multi-step forms went with them. What is left is the single signup. + await page.goto(base + '/'); + await page.locator('#updates').scrollIntoViewIfNeeded(); + const signups = []; + await page.route('https://ctrlrun-review-form.vercel.app/api/interest', async route => { + signups.push(route.request().postDataJSON()); + await route.fulfill({ status: 200, contentType: 'application/json', headers: { 'Access-Control-Allow-Origin': '*' }, body: JSON.stringify({ ok: true, id: 'test-email' }) }); }); - await page.getByRole('button', { name: 'Send review request →' }).click(); - await page.getByRole('alert').filter({ hasText: 'Provider temporarily unavailable.' }).waitFor(); - assert((await page.locator('.cr-email-preview').innerText()).includes('has not been sent'), 'Provider failure never claims success'); - await page.getByRole('button', { name: 'Retry submission →' }).click(); - await page.getByRole('heading', { name: 'Review request submitted.' }).waitFor(); - assert(submissions.length === 2 && submissions[0].requestId === submissions[1].requestId, 'Uncertain email retries reuse the same idempotency key'); - assert(submissions[1].email === 'engineer@example.com', 'Work email is included for replies'); - assert(await page.getByRole('button', { name: 'Send review request →' }).count() === 0, 'Successful submission cannot be double-clicked'); - await page.unroute('https://ctrlrun-review-form.vercel.app/api/review'); + await page.locator('.cr-updates-form input[type=email]').fill('reader@example.com'); + await page.getByRole('button', { name: 'Keep me posted →' }).click(); + await page.locator('.cr-updates-done').waitFor(); + assert(signups.length === 1 && signups[0].intent === 'launch-updates', 'The signup posts the launch-updates intent'); + assert(signups[0].email === 'reader@example.com', 'The address reaches the endpoint'); + assert(!('company' in signups[0]) || !signups[0].company, 'The signup asks for nothing but an address'); + await page.unroute('https://ctrlrun-review-form.vercel.app/api/interest'); + assert(await page.locator('text=/Pro|Enterprise|Pricing/i').count() === 0, 'No commercial copy on the home page'); await page.setViewportSize({ width: 1280, height: 900 }); assert((await page.goto(base + '/docs')).status() === 200, 'Documentation landing responds without a redirect loop'); await page.locator('#sidebar').waitFor(); diff --git a/docs.json b/docs.json index 2a3554c..215a0fc 100644 --- a/docs.json +++ b/docs.json @@ -24,10 +24,6 @@ "href": "/docs", "label": "Docs" }, - { - "href": "/#pro-and-enterprise", - "label": "Free, Pro, Enterprise" - }, { "href": "https://github.com/CTRLRun/ctrlrun", "label": "GitHub" @@ -35,8 +31,8 @@ ], "primary": { "type": "button", - "label": "Protect my agent", - "href": "/protect-my-agent" + "label": "Protect your first action", + "href": "/docs/get-started/quickstart" } }, "navigation": { @@ -1231,6 +1227,16 @@ "source": "/ROADMAP", "destination": "/docs/ROADMAP", "permanent": true + }, + { + "source": "/protect-my-agent", + "destination": "/", + "permanent": true + }, + { + "source": "/risk-check", + "destination": "/", + "permanent": true } ] } diff --git a/docs.mdx b/docs.mdx index 08271f6..4cdf822 100644 --- a/docs.mdx +++ b/docs.mdx @@ -219,7 +219,7 @@ the framework's own interrupt, and a framework with no such primitive does not n {/* generated from the suite, pyproject and the soak (mdx) — run the generator */} - **Version 0.11.0**, on [PyPI](https://pypi.org/project/ctrlrun/), Python 3.11 and later, tested on 3.11 to 3.14. -- **6,232 tests**, every version specified before it was written and every requirement mutation-tested. +- **6,220 tests**, every version specified before it was written and every requirement mutation-tested. - **32 guarantees you can check in your own setup**, with `ctrlrun verify` against your policy, on your store's backend, in a scratch store it creates. - **One host: a file.** SQLite, no server, no ops. **Many hosts: Postgres**, the same guarantees, graded by the same suite. - **Soaked for 20m 0s on postgres**: 889,735 actions, 0 unattributed ambiguous outcomes, positive control fired. Nothing here establishes what only accumulates over days. [What it does not establish](https://ctrlrun.dev/docs/production/soak). diff --git a/docs/production/index.mdx b/docs/production/index.mdx index ad555ed..52c2cca 100644 --- a/docs/production/index.mdx +++ b/docs/production/index.mdx @@ -28,7 +28,7 @@ need. `test_the_first_line_of_the_section_says_which_store_and_why` asserts the {/* generated from the suite, pyproject and the soak (full) — run the generator */} - **Version 0.11.0**, on [PyPI](https://pypi.org/project/ctrlrun/), Python 3.11 and later, tested on 3.11 to 3.14. -- **6,232 tests**, every version specified before it was written and every requirement mutation-tested. [Read more](/docs/how-this-is-built). +- **6,220 tests**, every version specified before it was written and every requirement mutation-tested. [Read more](/docs/how-this-is-built). - **32 guarantees you can check in your own setup**, with `ctrlrun verify` against your policy, on your store's backend, in a scratch store it creates. [Read more](/docs/security/verify-guarantees). - **One host: a file.** SQLite, no server, no ops. **Many hosts: Postgres**, the same guarantees, graded by the same suite. [Read more](/docs/production/postgres). - **Soaked for 20m 0s on postgres**: 889,735 actions, 0 unattributed ambiguous outcomes, positive control fired. Nothing here establishes what only accumulates over days. [Read more](/docs/production/soak). diff --git a/generated/readiness.full.mdx b/generated/readiness.full.mdx index 7ec9139..d2e1208 100644 --- a/generated/readiness.full.mdx +++ b/generated/readiness.full.mdx @@ -1,6 +1,6 @@ {/* generated from the suite, pyproject and the soak (full) — run the generator */} - **Version 0.11.0**, on [PyPI](https://pypi.org/project/ctrlrun/), Python 3.11 and later, tested on 3.11 to 3.14. -- **6,232 tests**, every version specified before it was written and every requirement mutation-tested. [Read more](/docs/how-this-is-built). +- **6,220 tests**, every version specified before it was written and every requirement mutation-tested. [Read more](/docs/how-this-is-built). - **32 guarantees you can check in your own setup**, with `ctrlrun verify` against your policy, on your store's backend, in a scratch store it creates. [Read more](/docs/security/verify-guarantees). - **One host: a file.** SQLite, no server, no ops. **Many hosts: Postgres**, the same guarantees, graded by the same suite. [Read more](/docs/production/postgres). - **Soaked for 20m 0s on postgres**: 889,735 actions, 0 unattributed ambiguous outcomes, positive control fired. Nothing here establishes what only accumulates over days. [Read more](/docs/production/soak). diff --git a/generated/readiness.json b/generated/readiness.json index 4eb42e5..ecf8fa7 100644 --- a/generated/readiness.json +++ b/generated/readiness.json @@ -18,6 +18,6 @@ "positive_control": true, "unexplained": 0 }, - "tests": 6232, + "tests": 6220, "version": "0.11.0" } diff --git a/generated/readiness.mdx b/generated/readiness.mdx index c84c27b..5926c59 100644 --- a/generated/readiness.mdx +++ b/generated/readiness.mdx @@ -1,6 +1,6 @@ {/* generated from the suite, pyproject and the soak (mdx) — run the generator */} - **Version 0.11.0**, on [PyPI](https://pypi.org/project/ctrlrun/), Python 3.11 and later, tested on 3.11 to 3.14. -- **6,232 tests**, every version specified before it was written and every requirement mutation-tested. +- **6,220 tests**, every version specified before it was written and every requirement mutation-tested. - **32 guarantees you can check in your own setup**, with `ctrlrun verify` against your policy, on your store's backend, in a scratch store it creates. - **One host: a file.** SQLite, no server, no ops. **Many hosts: Postgres**, the same guarantees, graded by the same suite. - **Soaked for 20m 0s on postgres**: 889,735 actions, 0 unattributed ambiguous outcomes, positive control fired. Nothing here establishes what only accumulates over days. [What it does not establish](https://ctrlrun.dev/docs/production/soak). diff --git a/generated/readiness.readme.md b/generated/readiness.readme.md index ddde915..1cc45e8 100644 --- a/generated/readiness.readme.md +++ b/generated/readiness.readme.md @@ -1,6 +1,6 @@ - **Version 0.11.0**, on [PyPI](https://pypi.org/project/ctrlrun/), Python 3.11 and later, tested on 3.11 to 3.14. -- **6,232 tests**, every version specified before it was written and every requirement mutation-tested. +- **6,220 tests**, every version specified before it was written and every requirement mutation-tested. - **32 guarantees you can check in your own setup**, with `ctrlrun verify` against your policy, on your store's backend, in a scratch store it creates. - **One host: a file.** SQLite, no server, no ops. **Many hosts: Postgres**, the same guarantees, graded by the same suite. - **Soaked for 20m 0s on postgres**: 889,735 actions, 0 unattributed ambiguous outcomes, positive control fired. Nothing here establishes what only accumulates over days. [What it does not establish](https://ctrlrun.dev/docs/production/soak). diff --git a/index.mdx b/index.mdx index d6d67e3..0428a41 100644 --- a/index.mdx +++ b/index.mdx @@ -11,7 +11,7 @@ canonical: "https://ctrlrun.dev/" import { HomeSlides } from "/snippets/home-slides.jsx"; import { HowDiagram } from "/snippets/how-diagram.jsx"; -import { CommercialTiers } from "/snippets/commercial-tiers.jsx"; +import { LaunchUpdates } from "/snippets/launch-updates.jsx";
@@ -21,7 +21,7 @@ import { CommercialTiers } from "/snippets/commercial-tiers.jsx";

CTRLRun stops AI agents from taking wrong, restricted, or malicious actions in your workflows.

Every action is checked against your rules before it runs. Allowed actions go through. Sensitive ones wait for a person. Forbidden ones are blocked.

- Protect my agent + Protect your first action GitHub

Open source · one line, any action{'@ctrlrun.protect("your.action")'}

@@ -67,40 +67,13 @@ import { CommercialTiers } from "/snippets/commercial-tiers.jsx";

Interactive walkthrough: follow one agent action through every check →

-
-
-
-

FREE, PRO, ENTERPRISE

-

Free and open source: the boundary. Pro: the boundary, run for you. Enterprise: the boundary, shaped to you.

-
-
-
- FREE · OPEN SOURCE -

ctrlrun Open Source

-

Free. Run it yourself.

-

The boundary itself, at no cost. No account, no card, no call with us. Every rule that decides is code you can read, and every action leaves a receipt you keep. Free to use and free to change, under the Apache-2.0 licence.

-

Who does the work Your team, on your machines.

- Install it -
-
- IN DEVELOPMENT -

ctrlrun Pro

-

Integrate, analyze, protect. One dashboard.

-

Connect your agents and workflows and see what each connection covers. Search every action, decision and outcome across your agents. Manage policies and approvals from one place, test a rule in observe mode, then turn enforcement on. Built on the open-source foundation, run by us.

-

Who does the work Your team, on our dashboard.

- -
-
- ENGAGEMENTS OPEN -

ctrlrun Enterprise

-

The same product, shaped to your company.

-

Everything in Pro, with CTRLRun engineers who design, integrate and maintain the controls your business needs: custom policies and approval chains, connectors to your internal systems, your deployment options, the reports you ask for. Scoped to your requirements, delivered with your team.

-

Who does the work Our engineers, with your team.

- -
-
+
+
+

WHAT COMES NEXT

+

Something is coming that makes controlling agents easy for your business.

+

CTRLRun today is a library you install and wire in yourself. What we are building next takes the same boundary and makes it something a team runs without writing the wiring. Leave an address and we will tell you when it is ready. Nothing else, and no one else gets it.

+
-
Let agents act.
Keep the consequences yours to decide.
diff --git a/protect-my-agent.mdx b/protect-my-agent.mdx deleted file mode 100644 index 9ae2f8c..0000000 --- a/protect-my-agent.mdx +++ /dev/null @@ -1,40 +0,0 @@ ---- -title: "Keep your stack. Add the boundary." -description: "An architecture review and integration help for agents in production. CTRLRun goes around the actions your agents take; we build the layers around it." -canonical: "https://ctrlrun.dev/protect-my-agent" -mode: "custom" ---- - -import { ArchitectureReview } from "/snippets/architecture-review.jsx"; - -
- ← CTRLRun -

FOR BUSINESSES RUNNING AGENTS IN PRODUCTION

-

Keep your stack.
Add the boundary.

-

CTRLRun sits between the agents you already run and the systems they act on.

-

Your agents keep their code and their framework. CTRLRun wraps only the calls that change something: money, infrastructure, permissions, records, messages. Whatever that boundary needs in production, we build and run.

- -
-
Your agents, tools and workflows
- -
CTRLRun: the execution boundary
-
- Pro and Enterprise: how that boundary is governed -
    -
  • Policies and approvals in one place
  • -
  • Activity across every agent
  • -
  • Operational risk you can see
  • -
  • Controls built for your stack
  • -
- The boundary is the same in every tier. Compare them ↗ -
- -
Your production systems
-
-

You deploy the agents. The controls around them become our job.

- - - -
Execution-safety review: where it starts

A paid architecture review of tool boundaries, approval flows, duplicate execution, uncertain outcomes and action records. You leave with an execution-control map and a prioritized plan: what can run automatically, what needs a person, and what must stop until the outcome is known.

Production Integration Sprint: the boundary into your stack

Hands-on integration, policy design, approval workflows, persistence, retry handling, reconciliation and production testing. Rollout is per workflow: pick one, agree the rules and who approves what, run it in observe mode to see what would have been blocked, then turn enforcement on.

ctrlrun Pro: integrate, analyze, protect. One dashboard

Connect your agents and workflows and see what each connection covers. Search every action, decision and outcome across your agents. Manage policies and approvals from one place, test a rule in observe mode, then turn enforcement on. Built on the open-source foundation, run by us. In development. Request early access ↗

ctrlrun Enterprise: the same product, shaped to your company

Everything in Pro, with CTRLRun engineers who design, integrate and maintain the controls your business needs: custom policies and approval chains, connectors to your internal systems, your deployment options, the reports you ask for. Scoped to your requirements, delivered with your team. This is what the review above starts.

Ongoing Production Support

A recurring engagement for new actions, policy changes, upgrades, incident analysis and rollout reviews.

-

Building it yourself? ctrlrun Open Source is free. Install it →

-
diff --git a/risk-check.mdx b/risk-check.mdx deleted file mode 100644 index 117d9ec..0000000 --- a/risk-check.mdx +++ /dev/null @@ -1,10 +0,0 @@ ---- -title: "Agent Execution Risk Check" -description: "Six questions to find where your agent architecture needs execution controls. A free check with practical next steps." -canonical: "https://ctrlrun.dev/risk-check" -mode: "custom" ---- - -import { RiskCheck } from "/snippets/risk-check.jsx"; - -
← CTRLRun

SIX QUESTIONS. A CLEARER EXECUTION BOUNDARY.

Agent Execution
Risk Check.

Find the gaps before your agent takes the next action.

Your answers stay in this browser. No email needed to see your result.

diff --git a/scripts/render-how-diagram.py b/scripts/render-how-diagram.py index 250fccc..86a5e24 100644 --- a/scripts/render-how-diagram.py +++ b/scripts/render-how-diagram.py @@ -39,7 +39,7 @@ ("Outcome unknown", ["No retry until a person resolves it.", "Nothing runs twice on a guess."]), ] -# The two ends name what the reader already has, in the vocabulary /protect-my-agent uses. +# The two ends name what the reader already has, in the vocabulary the home page uses. # No worked example: the process is the point, and a refund makes it look like a payments tool. TOP = ( "YOUR AGENTS, TOOLS AND WORKFLOWS", diff --git a/snippets/commercial-tiers.jsx b/snippets/commercial-tiers.jsx deleted file mode 100644 index 62ad124..0000000 --- a/snippets/commercial-tiers.jsx +++ /dev/null @@ -1,117 +0,0 @@ -// The action on one commercial tier card: the button that opens its form, and the form. The card -// around it -- status, name, promise, body, who does the work -- is static markup in index.mdx, -// and so is the whole Open Source card, whose action is a link and needs nothing from here. -// -// That split is the point. Mintlify renders a component imported from /snippets on the client, so -// everything this file used to draw was absent from the server-rendered HTML: a crawler, and any -// fetcher that does not run JavaScript, got the three section headings and none of the three -// tiers. Roughly 190 words of the page's plainest prose was invisible, which is most of why the -// home page measured 264 words. The prose moved into the page; only what needs state stayed here. -// -// One instance per card, named by `intent`, so each card keeps its own fields. A card whose form -// is open is bordered by `.cr-tier:has(.cr-tier-form)` in style.css, because the class that used -// to say so was on a card this file no longer owns. -// -// Both tiers post to /api/interest, which decides the subject line and the required fields from -// `intent` rather than trusting anything the browser sends. -// -// ENDPOINT and TIERS live inside the component for the original reason: a snippet exports one -// binding, and module-level constants beside it are not in scope when MDX evaluates it. -export const CommercialTiers = ({ intent }) => { - const ENDPOINT = 'https://ctrlrun-review-form.vercel.app/api/interest'; - - const TIERS = [ - { - intent: 'pro-waitlist', - cta: 'Request early access', - field: 'agents', - label: 'What do your agents do?', - optional: true, - rows: 2, - max: 200, - placeholder: 'For example, a support agent that issues refunds', - submit: 'Join the waiting list →', - done: 'You are on the list. We will write to that address when Pro opens up.' - }, - { - intent: 'enterprise-contact', - cta: 'Discuss your deployment', - field: 'message', - label: 'What does your deployment need?', - optional: false, - rows: 3, - max: 600, - placeholder: 'Controls, integrations, deployment constraints, timeline', - submit: 'Send enquiry →', - done: 'Thank you. We will reply from contact@arpanghoshal.com to arrange a call.' - } - ]; - - const tier = TIERS.find(candidate => candidate.intent === intent); - - const [open, setOpen] = useState(false); - const [sent, setSent] = useState(false); - const [email, setEmail] = useState(''); - const [company, setCompany] = useState(''); - const [detail, setDetail] = useState(''); - const [website, setWebsite] = useState(''); - const [sending, setSending] = useState(false); - const [error, setError] = useState(''); - const requestId = useRef(null); - const sendingRef = useRef(false); - const track = name => window.dispatchEvent(new CustomEvent('ctrlrun:conversion', { detail: { name } })); - - // Hooks run before this: an unknown intent is a mistake in the page, not a state to render. - if (!tier) return null; - const eventName = suffix => tier.intent.replace(/-/g, '_') + suffix; - - const openTier = () => { - setOpen(true); - setEmail(''); setCompany(''); setDetail(''); setError(''); - requestId.current = null; - track(eventName('_opened')); - }; - - const send = async event => { - event.preventDefault(); - if (sendingRef.current || sent) return; - // One id per filled-in form: an uncertain response can be retried without a second email. - if (!requestId.current) requestId.current = crypto.randomUUID(); - sendingRef.current = true; setSending(true); setError(''); - try { - const payload = { intent: tier.intent, email, company, website, requestId: requestId.current }; - payload[tier.field] = detail; - const response = await fetch(ENDPOINT, { - method: 'POST', credentials: 'omit', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(payload), signal: AbortSignal.timeout(15000) - }); - const data = await response.json(); - if (!response.ok || data.ok !== true || typeof data.id !== 'string') throw new Error(data.error || 'We could not confirm submission. Please retry or email us directly.'); - setSent(true); track(eventName('_submitted')); - } catch (failure) { - setError(failure.name === 'TimeoutError' || failure.name === 'TypeError' ? 'We could not confirm submission. You can retry the same request safely, or email us directly.' : failure.message); - } finally { sendingRef.current = false; setSending(false); } - }; - - return ( -
- {sent &&

{tier.done}

} - {!sent && !open && ( - - )} - {!sent && open && ( -
-
- - - -