Krunal Prajapati

  • Problem
  • Who it’s for
  • The model
  • Principles
  • Sketches

Product

  • Ask
  • Generative UI
  • Plan and execute
  • Design mode
  • Agents
  • Design system

System design

  • Architecture
  • Data model
  • Scanning
  • Model layer
  • Token optimisation
  • Tools
  • Execution
  • Evaluation
  • Desktop app

Proof

  • How we worked
  • Outcome
  • What I learned

Escher

An AI interface for running a cloud estate. Ask about cost, security or reliability, and get an answer you can check and act on.

Role
Product designer. Built the prototype end to end, and now the frontend of the desktop app.
Company
Tessell
Platforms
Web prototype, Electron desktop build, Tauri app
Team
Two product designers, 2026
Scanning prodread-only · keys stay on this device0S3 buckets copied to the local estateAre any S3 buckets public?Searching the local estatequery_resources41 rowspublic_access: true1 matchAre any S3 buckets public?Public buckets1of 41sellers-exportss3:GetObjectPrincipal: *1AWS Config3 options, by blast radiusDelete the bucket3 hitBlock the account42 hitMake it private1 hitApproveRollback: 1 callMake it private · approvedPolicy savedWildcard removedBlock Public Access onAre any S3 buckets public?Public buckets0of 41Anonymous GET403ESCHER

Are any S3 buckets public?prod account41 bucketsdemo estate

local estate 0/41 · writes locked

A copy of the estate, on your device

The Escher server sends a scan. The app on your device runs it against AWS with read-only calls, using keys that never leave the device, and keeps what it finds.

Account
prod
Calls
Read-only, 12 at a time
Keys
In the device’s keychain
Copied
41 S3 buckets

One question in

You ask in plain English. Only the prompt goes up to the Escher server.

You

Are any of our S3 buckets actually public right now?

The server writes a query

The server sends a query back down, and the app runs it on the local estate. The answer comes from your data, not from the model’s memory.

query_resources

  1. 1type: s3-bucket
  2. 2public_access: true
  3. →41 rows checked, 1 match
  4. sellers-exports

An answer you can check

The row goes up, and the server turns it into interface. Every value on it points back to the estate.

Public
1 of 41 buckets
Bucket
sellers-exports
Grants
s3:GetObject to Principal: "*"
Objects
~12,400
Source
1AWS Config, bucket ACLs

Fixes, sized by blast radius

Ask Escher to lock it down and the server sends back options. Each one shows what else it would touch. The write line stays locked.

  1. Delete the bucket1 bucket, 2 services

    The export job and analytics break. ~12,400 objects are lost.
  2. Block public access for the account41 buckets, 1 CDN

    cdn-origin may stop serving the CDN.
  3. Make sellers-exports private1 bucket

    Every read came from internal roles. Presigned links keep working.Recommended

You approve, then it runs

Turning the key opens the write line. The app runs each step on AWS with your keys and reads it back before the next one.

  1. Policy savedrollback point
  2. "Principal": "*" removedread back
  3. Block Public Access onread back

2 writes · rollback: 1 call

The local copy catches up

The app reads the bucket back from AWS and updates the local estate. Ask again, and the answer is none.

  1. Read back from AWSprivate
  2. Anonymous GET403 Forbidden
  3. Local estate0 of 41 public

Scroll to run the question

A worked example from the prototype’s demo estate. The Escher server writes every script and every answer, the app on your device runs them, and AWS keys stay on the device.

The problem

Companies were forecast to spend $723 billion on public cloud in 2025, and nearly a third of it is wasted. When something breaks, the answer is spread across a dozen consoles, runbooks and people’s heads. AI can answer fast, but no engineer will put an answer they can’t check on call.

$723B

Public cloud spend forecast for 2025

Gartner
29%

Of cloud spend estimated to be wasted

Flexera, 2026
84%

Of organisations call managing cloud spend their top challenge

Flexera, 2025
60%+

Of outages cost $100K or more

Uptime Institute, 2022
Fig 1Seven tools, no connection7 tools · 6 loose cables · 0 links
An alert is firingSecurity, terminal, runbooks, the on-call phone, alerts, cost and latencyCables with nothing to plug intoEach tool holds part of the answer, and none of them talks to the others. During an incident, a person carries the context from one to the next.

Scattered

Cost, security and performance data live in separate tools, each with its own console.

Untrusted

A confident paragraph from a model isn’t evidence. Without sources, it can’t be acted on.

Manual

Fixes are still written by hand in Python, Bash and Terraform, one incident at a time.

The data to fix all of this already exists. Nothing connects it, checks it and acts on it safely.

Who it’s for

A staff-level SRE who owns production reliability and writes the runbooks today. Their test is simple: would I put this on call?

The same estate answers different questions for seven roles.

Primary userSRE

Owns production reliability. Asks about scaling, capacity, incident root cause and latency.

Admin

Connects the first accounts and gets the team started.

SecOps

IAM hygiene, open security groups, encryption and patching.

DevOps

Drift, network layout, runbook automation and tickets.

FinOps

Rightsizing, reserved coverage, orphaned resources and cost spikes.

Platform

Many accounts, scanned and read as one estate.

Compliance

Gaps against SOC 2, HIPAA, ISO 27001 and PCI DSS.

Each role has its own test journeys in the prompt matrix: 70 in all, four or five turns each.

The model

Every cloudops task is one of five steps: Design, Develop, Detect, Debug, Deploy. Building something new and fixing something broken both end at Deploy.

Build what should exist Fix what went wrong Design Develop Detect Debug Deploy
Reactive

An alert arrives with context

The signal comes already linked to the resources, owners and history around it.

Interactive

Ask anything

Plain-language questions, answered from the live estate.

Proactive

Escher proposes the work

It watches for drift and suggests fixes before anyone asks.

Principles

01

Grounded

Every claim cites live estate data. Missing data is said plainly, never filled in.

02

Safe

Fixes are plans you can read like a diff, with approval, dry run and rollback.

03

Earned trust

Automation moves from shadow to live on evidence. One switch pulls it all back.

Sketches

Ideas from stakeholders became HTML sketches people could click, long before they reached the prototype. There are 24 of them, from the knowledge base to what happens when the model goes down.

Turn journey sketch: five concepts for one Escher turn.
Turn journey. Five ways to show the wait between a question and its answer.
Execution in chat sketch: a storyline of a plan running inside the thread.
Execution in chat. A plan that runs inside the conversation.
Design mode sketch: the four nouns, tool, skill, playbook and agent.
Design mode. Tools, skills, playbooks and agents in one library.
Knowledge base as a chat sketch.
Knowledge base. Company documents you can ask questions of.
Notifications sketch: one inbox grouped by what needs you.
Notifications. One page, grouped by what needs you.
Model failover sketch: the composer when the model is down.
Model failover. What the composer does when a model is down.

Ask

Type a question and the answer comes back as interface: numbers, tables and cards built for that question, each one citing its source. Reports run in two passes. The first finds the facts and the second writes them up, so the writing can’t claim more than the data shows.

OperateDesign
  • New session⌘N
  • Cloud estate
  • Codebases
  • Integrations
  • Alerts
  • Automations

Analysis

  • Public exposure in prod142d

Executions

    SRSam RiveraOrg admin
    Artifacts

    Good afternoon, Sam

    What can I help you investigate?
    prodAskExecute
    • How do I fix the top security issue?
    • What did EBS snapshots cost us last month?
    • How does security posture compare across our 6 accounts?
    • Which teams own the non-compliant resources?
    • Are there any unhealthy targets in our target groups?
    Home: a greeting, the composer and questions to start from.

    Pass 1Chat, read-only tools

    1. 1query_resourcestype: s3-bucket41 buckets in prod
    2. 2analyze_postureservice: s31 public: sellers-exports
    3. 3aws_clis3api get-bucket-policys3:GetObject to Principal "*"
    4. 4query_blast_radiussellers-exportsRead by 2 services
    5. open_report"S3 exposure review"Captured, never run
    Tool results only

    Pass 2Report, no tools

    S3 exposure reviewWarning

    prod · 41 buckets

    Public buckets1 of 41
    Services reading it2

    sellers-exports lets anyone read its objects. Two services read from it, so make it private rather than delete it.

    Hover a number to see the call it came from.

    OperateDesign
    • New session⌘N
    • Cloud estate
    • Codebases
    • Integrations
    • Alerts
    • Automations

    Analysis

    • Public exposure in prod142d

    Executions

      SRSam RiveraOrg admin

      F-1Public exposure in prodprod

      Where is prod actually at risk? Scan it and show me what is worth recording.

      Worked for 9s

      Scanned all three accounts. Two issue types are worth recording: one is a security exposure, the other is reliability headroom.

      2 findings · 1 critical internet exposure

      The bucket is the one to act on today; the Lambda health cluster is worth watching, not fixing.

      1 S3 bucket with the public-access block disabledWith the public-access block off, a single policy can expose it.Affects sellers-exportsCriticalSaved
      3 Lambda functions at riskThe estate reports these resources as at-risk.Affects api, lambda-0020 +1 moreHighSave

      May 30, 2026 · 10:11 PM

      Hold on, that bucket is called sellers-exports. Isn’t being public the point of it?

      Worked for 8s

      The name is not the permission. sellers-exports describes what the bucket holds, not who may read it, and it is served through CloudFront…

      What can I help you investigate?

      1 S3 bucket with the public…SavedResolve finding

      #ufnd_xysj
      1 S3 bucket with the public-access block disabled

      With the public-access block off, a single permissive bucket policy or object ACL is enough to expose this data to anyone on the internet.

      Evidence Open all

      Bucket policyPublic access blockObject ACL

      Details

      Status
      Open
      Cloud profile
      prod
      Severity
      ↑ Critical
      Account
      prod
      Effort
      → Medium
      Resource
      sellers-exports
      Category
      Security
      Region
      us-east-1
      Source
      Public exposure in prod
      Detected
      May 30 · 4:21 PM
      Assignee
      SR Sam Rivera
      Saved
      May 30 · 10:30 PM
      The answer ranks what it found. Open a finding and its evidence, severity and owner sit beside the thread.

      Generative UI

      The model answers in OpenUI Lang, a small language for interface. The app renders it with Escher’s own components as it streams, so an answer is something you can check, compare and act on, not a paragraph to read.

      SROur AWS bill jumped 31% this month and finance is asking. What’s going on?

      Escher

      2Your AWS bill rose about 31% compared with last month. I looked at Cost Explorer grouped by service over the last 90 days and found three main drivers.

      First, a new EKS node group called prod-pool was created 12 days ago in us-east-1. It runs eight m6i.2xlarge instances and adds roughly $4,218 per month. Second, a one-year reserved instance renewal of $11,400 was posted on May 1. This is an annual charge rather than an ongoing increase. Third, data transfer out rose about six times between May 14 and 16, which is most likely an S3 backfill, although I can’t see the job itself.

      1If you exclude the one-time charges, the recurring increase is about $4.2k per month. You may want to consider right-sizing the prod-pool node group and setting up cost anomaly alerts so you catch this earlier next time.

      3End of answer

      Fetched Cost Explorer, compared with a 3-month baseline · 1.8s

      1Most of the 31% isn’t real growth. One line costs more every month; the other two are one-offs.

      2prod-pool is the only recurring increase

      A new EKS node group, prod-pool, adds $4,218 a month.

      3
      DriverKindThis month
      prod-pool node groupRecurring$4,218
      Reserved-instance renewalAnnual$11,400
      S3 data transferOne-off6× for 3 days

      4The renewal is an annual charge. The transfer spike looks like an S3 backfill, so I’m treating it as a one-off.

      The honest increase is about $4.2k a month. I’d right-size prod-pool first.

      5Right-size prod-poolSet a cost anomaly alertShow transfer by day
      1. The answer comes lastThe point, a recurring $4.2k, is in the third paragraph.
      2. No sourcesNothing says which figure came from where, or which ones are guesses.
      3. Nothing to act onThe next step is a suggestion you would have to type back.
      1. The answer comes firstOne or two sentences, then the evidence.
      2. The heading names the factNot “Findings” or “Cost”, but the thing you need to know.
      3. One component for one pointA table, because the point is a comparison.
      4. Every claim shows its sourceAnd how sure it is: evidence, inference or assumption.
      5. Next steps are buttonsTwo or three specific moves, one click each.
      Words before the answer104104 as text · 0 as interface
      Claims with a source00 as text · 4 as interface
      Next steps, one click away00 as text · 3 as interface

      Both answers use the same tool results, from the prototype’s cost story. The plain one is how a text-only assistant would put them.

      Built while it streams

      The first line names the parts of the answer, so the layout exists before any part is written. Each part renders the moment its line parses. A part that never parses shows a local error instead of breaking the thread.

      OpenUI Lang · streaming

      1. root = Stack([lead, head, drivers, take, next])
      2. lead = TextContent("Most of the 31% isn’t real growth.")
      3. head = TextContent("## prod-pool is the only recurring increase")
      4. drivers = Table([driver, kind, amount])
      5. driver = Col("Driver", ["prod-pool", "RI renewal", "S3 transfer"])
      6. kind = Col("Kind", ["Recurring", "Annual", "One-off"])
      7. amount = Col("This month", ["$4,218", "$11,400", "6× for 3 days"])
      8. take = TextContent("The honest increase is about $4.2k a month.")
      9. next = FollowUpBlock([f1, f2])
      10. f1 = FollowUpItem("Right-size prod-pool")
      11. f2 = FollowUpItem("Set a cost anomaly alert")

      0 of 11 statements · 0 waiting

      root
      lead

      Most of the 31% isn’t real growth.

      head

      prod-pool is the only recurring increase

      drivers
      driver
      Driverprod-poolRI renewalS3 transfer
      kind
      KindRecurringAnnualOne-off
      amount
      This month$4,218$11,4006× for 3 days
      take

      The honest increase is about $4.2k a month.

      next
      f1
      Right-size prod-pool
      f2
      Set a cost anomaly alert

      One question, one form

      The component follows the question: a ranking gets a table, a trend gets a chart, three or more numbers of one kind get metric cards. A count or a yes/no stays a sentence. The model can only use what the library defines, the OpenUI base set plus thirteen of Escher’s own.

      “What are we spending this month?”

      Monthly cost$41.2k▲ 8% vs SeptemberBudget $45k
      Metric

      “Which service costs the most?”

      $0k$10k$20k

      BarChartHover a bar

      “What are our most expensive resources?”

      orders-dbus-east-1$4,210
      warehouseus-west-2$3,880
      etl-jobsus-east-1$1,290
      cdn-logseu-west-1$412
      DataGridSort a column

      “How is this bucket exposed?”

      InternetPolicyBucketPrincipal: * opens it to anyone
      HtmlFigure

      “What would shorter log retention save?”

      $3,120 / mo
      WidgetDrag

      “Why did spend go up?”

      Spend rose 8% this month, mostly from RDS.

      Evidence · AWS Cost ExplorerScopeprod · us-east-1PeriodOct 2026Data as of Oct 2, 2026 · 2:04 PM
      CiteClick the marker

      “Lock down the public bucket.”

      1 of 2

      Which environment?

      QuestionnairePick one

      “Review S3 exposure across prod.”

      WarningS3 exposure review3 findings · 24 buckets
      Report

      Choosing the chart

      The first version picked a chart from the shape of the data, which produced charts where every bar was 1. Now the form follows the reader’s question, and charts that carry no information are blocked in code.

      BeforeChosen from the data
      1RDS1DocDB1Redshift1OAuth1SSH1Other

      Every bar is 1. The chart answers nothing.

      AfterChosen from the question
      Secrets1,2000 rotating0 encrypted with KMS

      One number and the two facts that matter.

      Answers you can check

      Every load-bearing claim carries a numbered citation that says where it came from and how sure it is. Records like findings never pass through the model at all: it writes the framing, the tool supplies the rows.

      1EvidenceObserved in a named source.

      Evidence · AWS Cost Explorerprod-pool node group: $4,218 / moScopeaccount · us-east-1First seen12 days agoTypeEKS · m6i.2xlarge ×8Data as of May 25, 2026 · 9:13 AM

      3InferenceA conclusion drawn from data.

      InferenceThe transfer spike is most likely an S3 backfillBasisDataTransfer-Out +6× on 14–16 MayGapno job ID, correlation onlyData as of May 25, 2026 · 9:14 AM

      4AssumptionTaken as true, not yet confirmed.

      AssumptionTreating the backfill as one-off, not recurringWhyno recurring schedule in 90 daysRiskif it recurs, ~$300/mo moreData as of May 25, 2026 · 9:14 AM

      Modelbuild_findings tool

      2 findings · 1 critical internet exposureheadline

      The bucket is the one to act on today; the Lambda health cluster is worth watching, not fixing.lead

      1 S3 bucket with the public-access block disabledAffects sellers-exportsCriticalrow

      Anyone on the internet can read every object in it.note

      3 Lambda functions at riskAffects api, lambda-0020 +1 moreHighrow

      Watch, don’t fix: all three share one health signal.note

      The model can’t type a severity, a resource or a count. It writes the framing, and the rows arrive from the tool through React context.

      Escher’s own charts

      Band-scale bars whose labels are measured in the browser, so a long name is trimmed instead of running out of the chart.

      Figures and widgets

      A figure is sanitised and may load nothing from outside. A widget runs its script in a sandboxed frame with no access to the page.

      Guards in code

      No chart of zeros, no invented capitals in legends, and a section heading always stays with its figure.

      Plan and execute

      Start from a finding. Escher lays out ways to fix it, asks only what it can’t look up, and writes a plan with its blast radius up front. You review the change like a diff, dry-run it and approve it, and fresh reads prove it landed.

      OperateDesign
      • New session⌘N
      • Cloud estate
      • Codebases
      • Integrations
      • Alerts
      • Automations

      Analysis

      • Public S3 buckets142d

      Executions

      • F-1Fix sellers-exportsnow
      SRSam RiveraOrg admin

      F-1Fix sellers-exportsprod

      Start from the findingAsk about it, or plan a fix.
      F-1 · S3 bucket allows public readWhat breaks if we close it?

      Working2 calls

      query_estatereaders of sellers-exports

      0.4s

      2 readers: exports-cdn, batchgraph

      aws_cliaws cloudfront get-distribution-config --id E2K4QX9V

      0.9s

      Origin is the public S3 URLlive

      Escher

      Two things read sellers-exports, and closing it outright breaks one of them.1

      exports-cdnCloudFront · Reads over the public URLBreaks
      batchEC2 · Reads through its IAM roleUnaffected
      Plan a fixShow the policy

      Plan a fix.

      Working1 call

      query_estateblast radius of 3 options

      0.6s

      2, 41 and 3 resourcesgraph

      Three ways to fix it

      Make it private behind the CDNRecommended

      Block public access and let exports-cdn read through origin access control.

      2 resourcesLow riskReversiblePlaybook · 34 runs

      Block public access for the account

      Closes every bucket in prod, including two that serve public assets.

      41 resourcesHigh riskReversible

      Delete the bucket

      Removes the exports and breaks exports-cdn and batch.

      3 resourcesCan’t undo

      Make it private behind the CDN.

      exports-cdn: keep, through origin access controlRun: nowTell: #seller-platform

      Planning

      create_planoption: "private behind the CDN"

      captured, not run
      Make sellers-exports privatePlan · 5 steps · low riskReady

      exports-cdn gets read access in step 3, before the block goes on in step 4, so seller downloads never stop.

      Prepare the change set

      Prepare the change set.

      Thinking

      create_changesetplan: "P-1"

      captured, not run
      5 files · +27 −1Change set · 3 from the playbook, 2 newReview

      Run 5 steps on sellers-exports?Dry run it first.

      Dry runApprove and run

      Running step 1 of 5

      Done, and checked. sellers-exports is private, exports-cdn still serves files, and F-1 is resolved.

      Posted to #seller-platform
      F-1Ask about this finding…

      A few questions before I plan1 of 3

      • exports-cdn: keep, through origin access control
      • Run: now
      • Tell: #seller-platform

      Should exports-cdn keep serving these files?

      1Yes, through origin access control2No, take it offlineSomething else…

      When should it run?

      1Now2Next change window · Sat 02:00Something else…

      Who should hear about it?

      1#seller-platform on Slack2NobodySomething else…

      FindingPlanChange setRun

      #ufnd_xysjAskResolve

      S3 bucket allows public read

      With the public-access block off, anyone on the internet can read every object in sellers-exports.

      Evidence

      Bucket policyPublic access blockCloudFront origin

      Details

      Status
      OpenResolved
      Severity
      ↑ Critical
      Resource
      sellers-exports
      Region
      us-east-1
      Rule
      s3-public-read
      Source
      Public S3 buckets

      Resolved by run E-1 · 4 checks attached as evidence

      #P-1
      Make sellers-exports private behind exports-cdn

      From the S3 block public access playbook (34 runs, no failures), plus two new steps for origin access control.

      Steps runs top to bottom

      1. 1Snapshot the policy and ACLplaybook
      2. 2Create origin access controlnew
      3. 3Let exports-cdn read the bucketnew
      4. 4Turn on the public access blockasks firstplaybook
      5. 5Remove the wildcard grantplaybook

      Parameters

      bucket
      sellers-exports
      distribution
      exports-cdn · E2K4QX9V
      region
      us-east-1
      profile
      prod
      snapshot
      s3://escher-artifacts/P-1/
      notify
      #seller-platform

      Blast radius

      Resources2
      RiskLow
      RollbackStep 1 snapshot
      Public internetloses access
      sellers-exportsS3
      exports-cdnkeeps access
      batchunaffected

      #C-15 files+27−10 of 2 new files reviewed

      01-snapshot-policy+3

      02-create-oac+9

      03-allow-cdn-read+7

      04-enable-pab+7

      05-strip-wildcard-grant+1−1

      01-snapshot-policy.pyplaybook

      1. +policy = s3.get_bucket_policy(Bucket="sellers-exports")
      2. +acl = s3.get_bucket_acl(Bucket="sellers-exports")
      3. +write_json(SNAPSHOT + "before.json", {"policy": policy, "acl": acl})
      1. +oac = cloudfront.create_origin_access_control(
      2. + OriginAccessControlConfig={
      3. + "Name": "sellers-exports-oac",
      4. + "OriginAccessControlOriginType": "s3",
      5. + "SigningBehavior": "always",
      6. + "SigningProtocol": "sigv4",
      7. + }
      8. +)
      9. +attach_oac(distribution="E2K4QX9V", origin="sellers-exports", oac=oac["Id"])
      1. +add_statement(bucket="sellers-exports", statement={
      2. + "Effect": "Allow",
      3. + "Principal": {"Service": "cloudfront.amazonaws.com"},
      4. + "Action": "s3:GetObject",
      5. + "Resource": "arn:aws:s3:::sellers-exports/*",
      6. + "Condition": {"StringEquals": {"AWS:SourceArn": DISTRIBUTION_ARN}},
      7. +})
      1. +s3.put_public_access_block(
      2. + Bucket="sellers-exports",
      3. + PublicAccessBlockConfiguration={
      4. + "BlockPublicAcls": True, "IgnorePublicAcls": True,
      5. + "BlockPublicPolicy": True, "RestrictPublicBuckets": True,
      6. + },
      7. +)
      1. −{"Effect": "Allow", "Principal": "*", "Action": "s3:GetObject", …}
      2. +# wildcard GetObject statement removed from the bucket policy

      Dry runreads live state, writes nothing

      2CloudFront+ origin access control sellers-exports-oac

      3Bucket policy+ allow cloudfront.amazonaws.com, exports-cdn only

      4Access block4 flags false → true

      5Bucket policy− allow Principal "*"

      No errors · 2 resources change · exports-cdn keeps access

      #E-1
      Make sellers-exports privateRunningDone
      1. 1Snapshot the policy and ACL1.1s
      2. 2Create origin access control3.2s
      3. 3Let exports-cdn read the bucket1.3s
      4. 4Turn on the public access block2.7s
      5. Step 4 closes public access. Continue?Stop runContinue
      6. 5Remove the wildcard grant1.4s

      Verification fresh reads, not the steps’ own output

      Public access blocks3api get-public-access-blockAll 4 flags true
      Anonymous readcurl -I sellers-exports.s3.amazonaws.com/…403 AccessDenied
      exports-cdncurl -I exports-cdn/latest.csv200 OK through CloudFront
      Postureanalyze_posture type: s30 public buckets in prod

      F-1 resolved · sellers-exports is private

      1 · Context2 · Blast radius3 · ask_user4 · Plan5 · Change set6 · Execution log7 · Verification

      Nothing sent yet

      Sent with the question

      findingufnd_xysj

      rules3-public-read

      resourcesellers-exports

      evidence3 items

      scopeprodfrozen for the session

      Estate graph

      exports-cdnreadssellers-exportspublic URL

      batchreadssellers-exportsIAM role batch-runner

      Asking from a finding sends the record with the question, so the answer starts from it.

      OptionResourcesUndoTrack record
      Private behind the CDN2yesplaybook · 34 runs, 0 failed
      Account-wide block41yesnone
      Delete the bucket3nonone

      Blast radius is a walk of the estate graph, not a guess by the model. The model writes the options; the numbers come from the tool.

      ask_userthe turn waits for answers

      cdnShould exports-cdn keep serving these files?Yes, through origin access control

      whenWhen should it run?Now

      notifyWho should hear about it?#seller-platform

      Looked up, not asked

      Who reads the bucketestate graph

      How batch reads itIAM role

      Has this fix worked before34 playbook runs

      create_plancaptured, then written in its own pass

      1. "steps": [
      2. { "n": 1, "source": "playbook", "id": "snapshot-policy" },
      3. { "n": 2, "source": "generated", "id": "create-oac" },
      4. { "n": 3, "source": "generated", "id": "allow-cdn-read" },
      5. { "n": 4, "source": "playbook", "id": "enable-pab", "gate": "ask" },
      6. { "n": 5, "source": "playbook", "id": "strip-wildcard" }
      7. ],
      8. "rollback": "restore:step-1"

      steps53 playbook, 2 new

      gatestep 4asks before it runs

      order3 before 4exports-cdn never loses access

      rollbackstep 1 snapshot

      blast2 resourcesfrom the estate graph

      01-snapshot-policy.pyplaybook v1, unchanged

      02-create-oac.pynew · review

      03-allow-cdn-read.pynew · review

      04-enable-pab.pyplaybook v1, unchanged

      05-strip-wildcard-grant.pyplaybook v1, unchanged

      dry runsimulated against live state

      reads4policy, ACL, access block, distribution

      writes0

      changes2 resources

      errors0

      1. 10:42:03run E-1 approved by Sam Rivera
      2. 10:42:03step 1 snapshot → s3://escher-artifacts/P-1/before.json
      3. 10:42:04step 2 create-origin-access-control → oac-7f3a
      4. 10:42:07step 3 put-bucket-policy, +1 statement
      5. 10:42:08step 4 waiting for confirmation
      6. 10:42:11step 4 confirmed by Sam Rivera · put-public-access-block
      7. 10:42:14step 5 put-bucket-policy, −1 statement

      The model has no path to the write endpoint. Each step is its own request, sent after a person approves it.

      CheckBeforeAfter
      Access block flags on0 of 44 of 4
      Anonymous GET200403
      exports-cdn GET200200
      Public buckets in prod10

      ufnd_xysj resolved, 4 reads attachedE-1

      Posted to #seller-platform

      Verification runs its own reads. It doesn’t trust a step’s exit code.

      Design mode

      Operators write playbooks by talking them through instead of scripting them. Imported runbooks become playbooks too, and each one keeps its success rate and evidence.

      OperateDesign
      • New session
      • Playbooks
      • Import
      • Tools
      • Knowledge
      • Playground
      • Agents
      • Skills

      Drafts · 4

      • RDS Read-Replica Failoverv0.3
      • GKE Autoscaler Tunev0.2
      • VNet Peering Meshv0.1
      • TLS Certificate Renewalv0.1

      Published · 17

      • Remediate Open Security Groupsv1
      • S3 Lifecycle Policyv1
      • EKS Node-Group Rotationv2
      • IAM Access-Key Rotationv1
      SRSam RiveraOrg admin
      Playbooks

      Good evening, Sam

      Here’s where your library stands.

      17Published
      4Drafts
      96%Avg success
      4Need attention
      Ask about your playbooks, or describe one to build…
      • Cross-Region Failover for Multi-AZ Databases is down to an 86% success rate. What’s failing in its recent runs?
      • Show me my 4 draft playbooks and what each one still needs before it can be published.
      • Walk me through my security coverage: what’s protected and where are the gaps?
      • I want to build a new playbook from scratch.

      AWS·Created·Published v1Run

      S3 Lifecycle Policy

      Apply tiered lifecycle rules to buckets to move cold objects to cheaper storage classes.

      Type
      IaC
      Language
      Terraform
      Category
      Cost optimization
      Steps
      4
      Duration
      ~2m
      Blast radius
      Low
      Successful runs
      17/18
      Evidence
      4/4 verified
      Last modified
      1 week ago

      coststorage

      Steps

      4 of 4 steps built · runs top to bottom

      Provided by the run target · region

      1. 1Pre-check & guardrailsAttached

        aws.pre_check_guardrailsreused

      2. 2Discover targetsAttached

        aws.discover_targetsgenerated

      3. 3Evaluate & planAttached

        aws.evaluate_planreused

      4. 4Apply the changeAttached

        aws.apply_lifecyclereused

      Ask Escher, or describe a change…
      The library at a glance, and questions to ask it.A playbook with its steps, runs and blast radius.

      Agents

      Agents start as drafts, run in shadow until their results earn trust, then go live one playbook at a time. One switch drops every agent back to propose-only.

      DraftDefined, not running ShadowWatches and proposes LiveActs, with approval reviewscorecard Break glass: every agent back to propose-only
      OperateDesign
      • New session
      • Playbooks
      • Import
      • Tools
      • Knowledge
      • Playground
      • Agents
      • Skills

      Drafts · 4

      • RDS Read-Replica Failoverv0.3
      • GKE Autoscaler Tunev0.2
      • VNet Peering Meshv0.1
      • TLS Certificate Renewalv0.1

      Published · 17

      • Remediate Open Security Groupsv1
      • S3 Lifecycle Policyv1
      • EKS Node-Group Rotationv2
      • IAM Access-Key Rotationv1
      SRSam RiveraOrg admin

      Agents

      New agent

      Specialists that step in when a task or alert matches their patch. They earn autonomy in stages: defined as a draft, watched in shadow, then live.

      Drop all agents to propose-onlyBreak glass is on · restore4 live · 1 shadow · 1 draft · 1 paused47 resources uncovered

      Escher drafted an agent: Cert-expiry watcherYour team renewed 9 TLS certs by hand in the last 60 days, 2 within 48h of expiry.

      Review draft

      All agents · 7

      • Postgres guardianlive

        Keep the Postgres fleets healthy: failover readiness, replication, patching.

        Acts with approval · DB Platform · stepped in 2h ago

      • Backup wardenlive

        Every backup verified, every restore path tested.

        Acts with approval · DB Platform · stepped in 6h ago

      • Cost sentinelliverising rejections

        Catch spend anomalies before finance does.

        Proposes · Platform · stepped in 12d ago

      • SecOps triagelive

        First read on security bulletins and cert expiry, org-wide.

        Observes · Security · stepped in 1d ago

      • Disk-pressure medicshadow

        Handle the disk-pressure alerts the DB team keeps clearing by hand.

        Proposes · DB Platform · stepped in 3h ago

      • K8s medicpaused

        Node health and workload pressure on the prod-eu clusters.

        Acts with approval · Runtime · stepped in 2d ago

      • Network scoutdraft

        VPC and DNS watch for prod-us.

        Observes · Runtime

      Ask Escher, or describe a change…
      Every agent that can act drops to propose-only. Observers stay as they are.

      Strictest rule wins

      Three settings decide what an agent may do: its own mode, the grant on each playbook, and one org-wide break-glass lever. The strictest of the three always wins.

      Fig 2Agent permissionClick a stopact with approval · set by agent mode + playbook
      Carriage: what the agent may doStops, back to front: agent mode, playbook grant, break glassBreak-glass knobA spring pushes the carriage toward act with approval, and each stop can hold it back, so it always rests on the strictest one. The ruler reads observe, propose and act with approval, left to right. Break glass moves the front stop to propose for every agent at once.

      Design system

      One token file builds CSS variables, a Tailwind theme and typed tokens, and every app reads from it. Change a colour once and every app picks it up.

      Ink#1D1C1B

      Paper#F8F8F7

      Stone#EAEAE7

      Escher#FA504A

      Action#0265DC

      Success#007C0F

      Warning#E8C600

      Danger#D31510

      Aa

      Geist. Interface and reading text.

      exit 0 · 1.1s

      Geist Mono. Code, IDs and logs.

      tokensone source fileCSS variablesTailwind themeTyped tokensEvery appprototype, desktop, Tauri
      89

      Semantic tokens: colour, space, radius, type, shadow

      1

      Source file, built into CSS, Tailwind and TypeScript

      0

      Changes needed in each app when a token changes

      Architecture

      Three layers. The browser composes each turn and renders the answer. A thin API layer talks to models, the estate database and AWS. Every claim in an answer traces back to a row or a command output.

      BrowserAPIServices Chat controllerPrompt, tools, up to 3 roundsGen-UI rendererOpenUI Lang to componentsRepositoryVersioned stores, migrations /api/chatPrompt cache, retry, failoverEstate toolsQuery, graph, semantic searchAWS CLIRead-only, allowlisted Claude, OpenRouterModel allowlistPostgres + pgvectorEstate, findings, embeddingsAWS accountsEvery type, every region

      One turn

      A question becomes a short loop of tool calls, then an answer built from what the tools returned.

      OperateDesign
      • New session⌘N
      • Cloud estate
      • Codebases
      • Integrations
      • Alerts
      • Automations

      Analysis

      • Public S3 bucketsnow
      • Public exposure in prod142d

      Executions

        SRSam RiveraOrg admin
        Artifacts

        Good afternoon, Sam

        What can I help you investigate?
        prodAskExecute
        • What did EBS snapshots cost us last month?
        • How does security posture compare across our 6 accounts?
        • Which teams own the non-compliant resources?

        Public S3 bucketsprod

        Are any of our S3 buckets actually public right now?

        Working0.0s5 callsData fresh

        Estate inventory

        Start with the estate: count the buckets, then find any with the public-access block off.

        query_resourcestype: "s3"

        0.3s

        41 buckets in prodcached

        analyze_posturetype: "s3"

        0.7s

        1 flagged: sellers-exports1 flagged

        Live check

        The estate says one is open. Confirm it against AWS before saying so.

        aws_cliaws s3api get-bucket-policy --bucket sellers-exports

        1.1s

        s3:GetObject for Principal "*"live

        aws_cliaws s3api get-public-access-block --bucket sellers-exports

        0.9s

        All 4 block flags are falselive

        What serves it

        Check what reads from it before suggesting a fix.

        aws_cliaws cloudfront list-distributions

        1.4s

        Origin of exports-cdnlive

        Escher

        Yes, one: sellers-exports. Its policy lets anyone read every object,3 and the public-access block that would stop it is off.4

        Public buckets1 of 4112in prod
        Public-access blockOff4all 4 flags false
        Served byexports-cdn5CloudFront origin

        Make it private and let exports-cdn read it through origin access control, rather than delete it.

        Write it up as a reportMake it privateShow the policy

        Write it up as a report.

        Thinking

        open_reporttitle: "S3 exposure review"

        captured, not run
        S3 exposure reviewReport · prod · 41 bucketsWriting
        Ask a follow-up…

        ReportFindingExport

        #R-1
        S3 exposure reviewWarning

        prod · 41 buckets · May 30, 2026

        Public buckets1 of 4112
        Served byexports-cdn5

        Summary

        sellers-exports lets anyone read its objects,3 and its public-access block is off.4 exports-cdn serves it,5 so make it private behind the CDN rather than delete it.

        Findings

        1 finding · 1 critical internet exposuremodel

        S3 bucket allows public readAffects sellers-exportsCriticalSaveSavedscan_findings

        Public access block disabled leaves bucket contents open to anonymous read until re-enabled.model

        #ufnd_xysj
        S3 bucket allows public read

        With the public-access block off, anyone on the internet can read every object in sellers-exports.

        Evidence Open all

        Bucket policyPublic access blockCloudFront origin

        Details

        Status
        Open
        Severity
        ↑ Critical
        Resource
        sellers-exports
        Region
        us-east-1
        Source
        Public S3 buckets
        Rule
        s3-public-read
        Detected
        May 30 · 4:21 PM
        Saved by
        SR Sam Rivera
        1 · Request2 · Tool loop3 · Stream4 · Pass 25 · Record

        Nothing sent yet

        POST/api/chat

        modelclaude-opus-5-5

        systemchat rules + gen-UI grammarcache hit

        scopeprodfrozen for the turn

        roundsup to 3

        Tools 14

        query_estatequery_resourcesquery_findingsscan_findingsanalyze_posturebuild_findingssearch_knowledgequery_reportsaws_cliopen_reportcreate_plancreate_changesetrun_executionask_user

        runs, returns datacaptured, never runpropose_change run_steps not sent in Ask

        modelcallanswerRound 1Round 2Round 3
        model
        query_resourcestype: "s3"41 buckets
        analyze_posturetype: "s3"1 flagged
        aws_cliget-bucket-policyPrincipal "*"
        aws_cliget-public-access-block4 flags false
        aws_clicloudfront list-distributionsexports-cdn
        0s2s4s6s8s

        Each aws_cli call runs as an argument list, with no shell:["aws", "s3api", "get-bucket-policy", "--bucket", "sellers-exports", "--profile", "prod"]

        1. root = Stack([lead, stats, take, next])
        2. lead = TextContent("Yes, one: sellers-exports. Its policy lets…")
        3. stats = Stack([m1, m2, m3], "row")
        4. m1 = Metric("Public buckets", "1 of 41", null, null, null, "in prod")
        5. m2 = Metric("Public-access block", "Off", null, null, "bad")
        6. m3 = Metric("Served by", "exports-cdn")
        7. take = TextContent("Make it private and let exports-cdn…")
        8. next = FollowUpBlock([FollowUpItem("Write it up as a report"), …])

        0of 8 statements

        The first line names four parts, so the layout is on screen before any of them is written. Each part fills in when its line parses.

        Pass 1chat

        tools14

        calls5 run

        open_reportcaptured, never run

        5 tool results

        Pass 2report

        toolsnone

        context5 tool results

        writesS3 exposure review

        Pass 2 has nothing it can call, so the report can only use what pass 1 found.

        scan_findingsrun by the app, no model call

        1. {
        2. "fingerprint": "s3-public-read:sellers-exports",
        3. "ruleSlug": "s3-public-read",
        4. "resourceId": "sellers-exports",
        5. "severity": "critical",
        6. "count": 1
        7. }

        RowsFrom the scan. The model can’t type a severity, a resource or a count.

        Headline and notesFrom the model, keyed to each row’s fingerprint.

        Saved to the findings storeufnd_xysj

        Safety in code, not in the prompt

        A role named read-only can still carry write permissions, so the checks live in the API, not in IAM or the system prompt.

        Reads

        • Commands run as an argument list with no shell, so ;, pipes and $() mean nothing.
        • Only allowlisted read verbs run.
        • The profile must exist on the machine, or the call is refused.
        • Every call has a timeout and an output cap.

        Writes

        • The model can only propose a change. Its tool loop has no path to the write endpoint.
        • Each step runs only after a person clicks it.
        • The first step records the current state, so rollback restores it.
        • Model IDs are allowlisted, and the deploy sits behind a signed password cookie.
        31

        API routes

        12

        Database migrations

        3

        Tool rounds per turn, at most

        52

        Tools the model can call

        • React, ViteThe frontend, shared by the web prototype, the desktop build and the Tauri app.
        • Anthropic, OpenRouterNative Messages API for prompt caching, with failover to another model when one is down.
        • Postgres, pgvectorThe estate, findings, reports and alerts, plus embeddings for semantic search.
        • pnpm, TurborepoOne monorepo for the apps, the design system and the estate store.
        • VercelEdge functions, password-gated deploys, share links in Vercel Blob.
        • Tauri, ElectronThe shipping desktop app, and the build that runs the prototype on real accounts.

        Data model

        The estate is thirteen Postgres tables. resources holds current state. Everything around it keeps the history: what changed, what each resource looked like before a scan, how resources connect, and an embedding of each one for search.

        Scans

        Estate

        History and search

        Work

        resources Current state of every resource, one row per profile, account and ARN. A deleted resource stays, marked deleted.

        Primary keyLinks to another tableChoose a table to trace it.

        All or nothing

        A scan is classified in one transaction, so a half-finished scan can never read as a complete one.

        Diff by hash

        Each resource has a content hash. Unchanged resources are left alone, and a deleted resource that comes back counts as new.

        Encrypted

        Sensitive payloads use AES-256-GCM with a fresh IV each time, so matching resources never produce matching ciphertext.

        Scanning

        Every resource type in every region is its own read-only call. They all go into one queue that runs twelve at a time, so the limit is AWS throttling, not the order of the work.

        A call that fails is recorded and retried with backoff. A scan that couldn’t look never reports a clean estate.

        r1r2r3r4r5r6r7r8r9r10r11r12r13r14r15r16
        EC2
        S3
        RDS
        Lambda
        IAM
        VPC
        EKS
        KMS
        ELB
        ScannedRunningFailed, recorded, retried0 in flight · 0 of 144 done
        24

        AWS services in the registry

        12

        Calls in flight at once

        6h

        Between automatic rescans on the desktop build

        Model layer

        Every route speaks Anthropic’s Messages API, because prompt caching only exists there. Claude models go straight to Anthropic. Other models go through OpenRouter’s Anthropic-format endpoint, so the request and the stream parser stay the same.

        Only model IDs on an allowlist reach a provider. If a model has no key configured, the user gets a sentence explaining why, not a dead stream.

        Requestmodel id, toolsAllowlistunknown id → 400RouterAnthropicclaude-*, cached promptOpenRoutervendor/modelBack to the browser, one streamtool calltool calltokensText renders as it arrives. Tool calls run in the browser. The last marker reports token use.

        Token optimisation

        Every request carried about 11,000 tokens before the conversation even started: a 9,000-token system prompt and 2,200 tokens of tool schemas. A report turn made five to eight model calls, and each one re-read the whole conversation.

        I measured it first, then cut it without changing a single answer.

        138k billed input, in token-equivalents

        Re-read in fullRead from cache, 0.1×New this call, cache write 1.25×

        An example report turn of eight calls. Bottom to top: tool schemas, system prompt, conversation so far, the new turn. Prices are the published cache multipliers.

        ~11k

        Tokens of fixed prefix on every request

        5–8

        Model calls in one report turn

        0.1×

        Price of a cached read

        53

        Sources in the token-efficiency survey

        Three tiers

        Every idea was sorted by how much it could change an answer. Only the first tier ships without an evaluation.

        A · Lossless

        Ship it

        • Cache the conversation prefix, not just the system prompt
        • Keep the cached prefix stable across modes
        • Remove dead and duplicate prompt text
        B · Low risk

        Spot-check and canary

        • Clear stale tool results from long loops
        • Compact output formats
        • A sane output ceiling, with an alert if it clips
        C · Risky

        Evaluation gate first

        • Lower reasoning effort on easy turns
        • Summarise long history
        • Route easy turns to a cheaper model

        Measured

        The thinking panel breaks every turn down by model call, with cache reads and writes shown apart.

        Honest cost

        Cost is shown cache-discounted, so the number on screen matches the bill.

        Lossless first

        Prompt caching was the biggest win, and the provider guarantees it leaves output unchanged.

        Tools

        The model works through 52 tools in four groups. Control tools are the important ones: the app captures them and shows them to a person, and they never run on their own.

        17

        Read

        The estate, AWS, the knowledge base, Slack and Linear.

        • query_estate
        • query_resources
        • search_estate
        • query_service_topology
        • query_blast_radius
        • query_findings
        • query_reports
        • query_playbooks
        • search_knowledge
        • analyze_posture
        • aws_cli
        • aws_api_batch
        • scan_repo
        • fetch_url
        • linear_tickets
        • slack_channels
        • slack_users
        5

        Record

        Turn tool results into findings, reports and plans.

        • build_findings
        • scan_findings
        • create_plan
        • open_report
        • open_estate_topology
        12

        Control

        Proposals a person accepts or rejects.

        • propose_write_plan
        • revise_plan
        • skip_execution_step
        • ask_user
        • request_review
        • propose_slack_post
        • propose_contract
        • propose_readiness
        • propose_task_outline
        • offer_execution_session
        • offer_playbook_session
        • suggest_replies
        18

        Author

        Build a playbook step by step in Design mode.

        • add_step
        • add_tool
        • add_guardrail
        • add_parameter
        • add_risk
        • add_capability
        • add_knowledge
        • set_trigger
        • set_gate
        • set_approval
        • set_blast_radius
        • set_context
        • set_scenario
        • set_language
        • wire_step
        • test_step
        • run_end_to_end
        • publish_playbook

        Execution

        The model can propose a change but has no way to run one. Each step runs only after a person confirms it. The first step records the current state, each step is re-read after it runs, and a step can stop the run or ask before going on.

        Proposethe modelConfirma personSnapshotcurrent stateRunone stepVerifyre-read itRollback restores the snapshothalt or askcannot run anything

        Evaluation

        Answers are graded by six plain functions, not by another model. They check that every number came from a tool result, that knowledge answers cite a source, and that the interface code parses. They run in CI with no API key.

        Tool results

        query_resources → {"type": "ebs-snapshot", "count": 5152,  "byAccount": {"prod": 3904, "data": 1248}}search_knowledge → {"count": 2, "results": ["Snapshot retention", …]}

        Answer, in OpenUI Lang

        root = Stack([head, kpi, grid, cite, more])head = TextContent("Prod holds 4,812 of 5,152 snapshots")kpi = Metric("EBS snapshots", "5,152")grid = Table([Col("Account", accts), Col("Snapshots", counts)])cite = Cite(1, "evidence", "Snapshot retention runbook")more = FollowUpBlock(["Which are older than 90 days?"])
        1. groundingEvery number is in a tool result
        2. citationKnowledge answers cite a source
        3. parseThe interface code parses
        4. shapeNo empty or one-column parts
        5. absenceEmpty results aren’t read as “none”
        6. reachableEvery part is on screen

        numeric claim "4,812" does not appear in this turn’s tool results

        The model rounded from memory. 4,812 is nowhere in what the tools returned.

        6

        Deterministic graders

        383

        Test files

        7

        Roles in the prompt matrix

        Desktop app

        The web prototype runs on a made-up estate. The Electron build runs the same app against real AWS accounts, with the database, credentials and scans kept on the laptop.

        On the laptopNetwork WindowThe same web app, unchangedLocal serverThe same API routes, one origin Embedded PostgresPGlite with pgvector, in WASMAWS CLI, Python, boto3Bundled, using local profiles OSKeychain for keys · escher:// sign-in links · native alerts · auto-update ModelsPrompts onlyAWS APIsYour accounts

        Real accounts

        It scans every resource type in every region of a connected account, rescans every six hours and keeps the history.

        Local first

        The database runs inside the app and keys live in the OS keychain. Only the prompt goes out to the model.

        Native

        System notifications, Slack and Linear sign-in through escher:// links, and updates that install themselves.

        Web prototype

        Demos and design

        A made-up estate anyone can explore in a browser.

        Electron build

        Real accounts

        The same app on a laptop, connected to real AWS.

        Tauri app

        The product

        The shipping desktop app, using this frontend code.

        How we worked

        One repo, with one set of rules for people and AI agents: an agent roster, issue-first changes and a decision log that stops settled debates from reopening. Vision became sketches, approved sketches became the prototype, and the prototype’s code moves into the app.

        Vision Sketch Prototype Tauri app Stakeholder ideas Clickable HTML Working, demoable Shipped frontend
        573

        Commits since May 2026

        395

        Of them mine

        170+

        Decisions logged

        24

        Sketches

        Outcome

        Tessell uses the prototype to demo Escher to potential customers and investors. Its frontend code moves straight into the Tauri app, so the prototype works as a spec as well as a demo.

        Fig 3One frontend, two places1 frontend · 2 builds · same screen
        ESCHER · UIDEMO
        The frontend: React, one codebaseDemo screen, for customers and investorsLaptop, running the Tauri appThe prototype and the Tauri app load the same frontend, so a change made for a demo is already in the product. Both screens update together.

        What I learned

        A bigger role

        I started as a product designer making a prototype. Now I build the frontend of the desktop app as well.

        A different process

        Design happens in working code instead of static mockups. The sketch, the prototype and the shipped frontend are the same material.

        A faster pace

        Ideas go from sketch to something people can click in days, not weeks.

        Next projectOpenFOAM StudioDescribe a CFD simulation in plain English, and run the real solver.Read the case study →
        All projects