Limit agent permissions and spend in AWS Transform custom

Post Syndicated from Hamza Alfarrash original https://aws.amazon.com/blogs/devops/limit-agent-permissions-and-spend-in-aws-transform-custom/

Engineering teams that own large Java estates face recurring upgrade deadlines. When they hand one of those upgrades to an AI agent, the agent brings its own permissions and its own bill. Consider a central engineering group with a few hundred Java services, several of which run on a Java Development Kit (JDK) version that reaches end of support in two quarters. Upgrading each service by hand means a dependency audit, a javax to jakarta migration, a test framework migration, and a release. The deadline does not move while the team works through the list.

Agents change the arithmetic. An agent upgrades a repository in place, where it runs the build, edits the source, and commits as it goes, and it finishes in minutes a job that takes a developer weeks. Across a few hundred services you don’t sit at a terminal approving each step. You invoke the agent from a pipeline, repository after repository, and an autonomous process with shell access to your source code now runs throughout your estate.

That speed comes with a footprint you must manage. The agent runs shell commands against the code that it’s changing, it uses the AWS credentials in its environment, and it bills you by the agent minute while it works. Before you point it at a few hundred repositories, you decide what permissions the agent holds and how much it can spend. That decision repeats wherever a runtime deadline meets a service count too large to upgrade by hand.

AWS Transform custom provides the agent, and you enforce that decision with three configuration controls. You write a trust allowlist to set which of the agent’s actions run without asking you. You scope an execution identity to limit what the agent can ask the service to do. You also set a cap on agent minutes, which stops a run at the limit you choose, and you configure an account-level alert on cumulative spend across runs. None of the three requires new infrastructure, because you manage each one as configuration, so you can adopt agentic transformation on a schedule you set.

The agent runs transformations through the AWS Transform CLI, either interactively on your machine or non-interactively in a pipeline, so the controls must hold in both places. In this post, we walk through the three controls against a working repository and give the measured result of each one. We show how a real run produced the trust allowlist, why interactive runs and pipeline runs need different controls, and how to scope an execution identity to approved transformations. The complete source code is available in the sample repository.

Unbounded agent risks

An agentic transformation runs as a process on a machine you own, rather than handing you a patch to review, and that footprint carries three distinct risks. The agent can run tools without limit, the identity it runs under can do more than run transformations, and the spend it accrues has no ceiling unless you set one. Each risk needs its own control, and you meet all three on the way to your first fleet-wide upgrade.

Tool execution

The agent works by calling tools. It reads a file, writes a file, runs the build, inspects git history, and commits, and the shell commands among them run with the privileges of whoever started the run. On your first run against one service, each of those calls stops for your approval, and after enough prompts the flag that trusts every tool looks like the obvious way to get through the day. That flag stops the prompts by removing the check behind them, which is the risk. The agent can then run whatever a transformation asks of it, on a machine that holds your source code and your credentials.

Identity

The AWS credentials in your environment decide what the caller can do to the service, and the managed policy that gets a first run working also covers authoring. If an identity can both run a transformation and rewrite its definition, it can also change what runs next time. On a laptop that identity belongs to a person exercising judgment, but in a pipeline it belongs to a role that the rest of the build inherits, and the pipeline is where your other few hundred runs will happen.

Spend

Billing is per agent minute, and the meter runs while the service plans, reasons, analyzes, and edits. A single run on a small service costs little, so the risk isn’t the run you watch but a fleet-wide upgrade of a few hundred automated runs, on a bill nobody reads until the end of the month.

Solution overview

The following diagram shows where each of the three controls intercepts a transformation run.

Three controls on an AWS Transform custom run: trust settings, scoped execution identity, a spend cap and a budget alert

Figure 1: Where each of the three controls intercepts a transformation run

  1. Invocation: A developer runs the AWS Transform CLI interactively, or a pipeline runs it non-interactively. The two paths carry different controls.
  2. Trust settings: Before the agent runs a shell command or writes a file, the CLI checks the trust settings file in your home directory. A command that matches the allowlist runs. A command that does not match returns to the person at the terminal as an approval prompt.
  3. Identity: AWS Transform custom authorizes each call the CLI makes against the AWS Identity and Access Management (IAM) identity in your environment. When you attach the execute-only managed policy and scope it to the transformations you approve, the policy bounds the service actions that the caller can request, including requests that originate from the agent.
  4. Spend: The run carries an agent-minute cap and stops when it reaches it. Underneath that, an AWS Budgets budget filtered to AWS Transform watches monthly spend across runs and notifies a subscriber at a threshold you set.

Inside a transformation run

AWS/java-version-upgrade is one of the AWS managed transformations, prebuilt and maintained by AWS rather than authored in a customer account. Running atx custom def list shows the AWS managed definitions alongside any custom ones you have created. The definition for this one covers a range of build systems and source and target JDK versions, with dependency modernization that includes Jakarta EE migration, database drivers, object-relational mapping (ORM) frameworks, and updates across the Spring libraries.

The target version is an input rather than a property of the transformation. The definition gives three ways to supply it: interactive chat with the agent, an additionalPlanContext configuration parameter, or a configuration file. We specified the target version in the prompt we gave to atx.

The sample repository is a small Spring Boot REST service with a single endpoint, running on Java 8 and Spring Boot 1.5. It stands in for one of the few hundred services in the opening scenario. The transformation takes it to Java 17 and Spring Boot 3.2, so the javax.servlet and javax.annotation imports become their jakarta equivalents and the JUnit 4 test moves to JUnit 5. We kept the business logic trivial from the beginning so anyone can follow along. If the logic were more complicated, it would introduce a monetary barrier for readers who want to try it themselves, and it would take longer to run.

In a run (AWS Transform CLI 3.10.0), the transformation made 43 tool calls across the sample’s 4 files: 13 shell commands, 11 file writes, 11 file reads, 6 editor operations, and 2 version control operations. Every shell command ran with the permissions of the identity that started the run, inside the working tree of the repository being upgraded. The counts in this post come from this definition and this sample in August and September 2026, so expect different counts on other CLI versions, definitions, and repositories.

The documentation describes multi-step transformations as using Git to commit intermediate steps. In that run, the 2 version control operations produced a single checkpoint commit on the result branch.

Prerequisites

The walkthrough requires the following:

  • An AWS account and an AWS Region where AWS Transform custom is available, listed in the getting started guide.
  • The AWS Transform CLI and Node.js 22 or later, which the CLI requires. Install the CLI with curl -fsSL https://transform-cli.awsstatic.com/install.sh | bash, then confirm with atx --version. The confirmation runs in this post used version 3.10.0.
  • Git, with the sample checked out as a valid repository, because the transformation commits its intermediate steps. On CLI 3.10.0, a run also stopped when the working directory wasn’t a repository, which is behavior we measured rather than a documented requirement.
  • JDK 8 to build the sample before the upgrade, JDK 17 after it, and Maven 3.6 or later. The commands in this post were verified on macOS. On Linux, set JAVA_HOME to a JDK 8 path directly instead of using the macOS-specific /usr/libexec/java_home helper.
  • AWS credentials with permission to call AWS Transform custom. Control 2 covers which policy to attach.

Restricted networks additionally need transform-cli.awsstatic.com, transform-custom.<region>.api.aws, and *.s3.amazonaws.com reachable. The repository README carries the full setup detail.

Control 1: Scope which tools run without asking

This control starts on a laptop, because that is where the allowlist gets built.

Our first run of AWS/java-version-upgrade against the sample repository used AWS Transform CLI 3.9.0 with no trust configuration, and it raised 23 approval prompts across four files. The later runs used 3.10.0, and the CLI version history lists no change to trust settings or approval prompts in that release.

Before the allowlist, you have two options. Either you grant blanket tool trust for the session, which comes with the risk of tool calls you don’t want the agent to make. Or you walk through every prompt carefully, reading what it says and deciding whether you want that tool call to run, which adds to the overall time the agent takes to complete the task.

Across a few hundred repositories, either choice makes the documented shortcut the obvious thing to reach for. The --trust-all-tools flag (-t) approves the tool calls the agent requests without asking.

The middle position is a trust settings file at ~/.aws/atx/trust-settings.yaml. The file is split into three categories. trustedTools covers the tool calls that are allowed automatically. trustedShellCommands are the shell commands you allow the agent to run on its own without prompting you. And alwaysPromptCommands are the commands the agent has to ask you about before executing, in interactive mode. A fourth mechanism sits alongside the file: answering t at an approval prompt trusts that tool for the rest of the session, which the prompt text itself names when it reports that a tool “hasn’t been trusted this session.”

We first made a test run with no trust configuration in place, so we could see which tool calls the agent makes for this specific transformation. We logged each tool call and added the shape of each command to the file, to make it straightforward for you to use. Twenty-three prompts collapse into a short list, because the agent asks for variations of the same few operations.

Rerunning with that first list (CLI 3.10.0) dropped the count from 23 prompts to 3, and all three were the same shape: cd <path> && <command> compounds in the validation phase. The matcher is a full-command-string glob, so JAVA_HOME=* mvn * does not match a string that starts with cd. Command shapes vary between runs, and this one issued a bare mvn during execution and cd ... && mvn during validation, so the shipped file carries both forms. With both forms added, a third run on a clean clone (CLI 3.10.0) reached zero prompts.

The following excerpt shows the shape of the file. The repository copy carries the full set, including the cd-prefixed variants:

trustedTools:
  - editor
  - file_write
  - vcs_control

trustedShellCommands:
  - "mvn *"
  - "JAVA_HOME=* mvn *"
  - "git log *"
  - "git status"
  - "java -version*"

alwaysPromptCommands:
  - "rm -rf *"
  - "sudo *"
  - "find * -exec *"

Copy the file into place and run the transformation interactively:

mkdir -p ~/.aws/atx
cp guardrails/trust-settings.yaml ~/.aws/atx/trust-settings.yaml

atx custom def exec -n AWS/java-version-upgrade -p . \
  -c "JAVA_HOME=$(/usr/libexec/java_home -v 1.8) mvn clean verify"

The mkdir -p matters on a fresh install, because ~/.aws/atx/ does not exist until the CLI has run at least once.

Patterns match against the full command string, which is why JAVA_HOME=* mvn * is listed separately from mvn *. The validation command pins JDK 8 because Spring Boot 1.5 can’t start its context on later versions, which is what puts that prefix in the run at all.

The alwaysPromptCommands entries are matched against each sub-command in a compound expression rather than against the whole string. In an interactive run, session trust and the --trust-all-tools flag don’t approve a match.

Important: alwaysPromptCommands doesn’t force a prompt in 2 cases.

  • A matching trusted pattern takes priority. The documentation states that trustedShellCommands patterns match the full command string and take priority over the always-prompt list, so a trailing * spans pipes, semicolons, and &&. In our verification run (CLI 3.10.0), the trusted pattern /usr/libexec/java_home * matched a compound that continued ; echo "---" ; ls ..., and the chained tail ran without a prompt. By the same rule, mvn * trusts mvn --version; printf canary even with printf * in alwaysPromptCommands. The file in this post uses mvn * to reach zero prompts on the sample, so narrow that pattern to the Maven goals your builds run before you reuse the file.
  • Non-interactive mode (-x) does not enforce alwaysPromptCommands, as the documentation states, so the list has no effect on a pipeline run.

Keep the two lists non-overlapping and the trusted patterns as narrow as the runs allow. The following diagram shows the order in which the CLI evaluates a proposed shell command.

Decision flow: a trusted full-string match runs first, then always-prompt patterns, then tool and session trust

Figure 2: How the CLI evaluates a proposed shell command in interactive mode

With the allowlist in place, a full run of AWS/java-version-upgrade completed with no approval prompts and without blanket trust. The run made 43 tool calls, each one covered by the allowlist or by a built-in trusted tool, with a green build and 2 of 2 tests passing on AWS Transform CLI 3.10.0. This lets us focus on other tasks, and we’re only interrupted when the task finishes or something unexpected comes up. The startup banner on that version also identified six built-in trusted tools: file_read, grep, both transformation-registry tools, document_manager for add operations, and editor for view and find-line operations. Those calls don’t prompt, so a prompt inventory understates the agent’s effective tool surface. Default trust is also scoped by path rather than only by tool. The same file_write tool runs without prompting when it targets the CLI artifacts directory and prompts when it targets the repository.

In a pipeline

The trust settings file governs interactive runs only. On CLI 3.10.0, -x without --trust-all-tools stops before the transformation starts, with the error Must explicitly --trust-all-tools when running in non-interactive mode, which we hit in a test. A pipeline run therefore trusts every tool, and the allowlist has no effect on it. The interactive path does not fit a pipeline either, because without a terminal it stops at the plan approval step and reports Input is not a TTY.

This control therefore belongs on a developer machine. A pipeline needs its boundary somewhere else, and that somewhere else is the next two controls.

Control 2: Separate the identity that runs from the identity that authors

The pipeline from the opening scenario needs an identity, and that identity should run the upgrade and little else.

Three AWS managed policies cover AWS Transform custom, and the split between them is the control. The following table describes each policy at version 4, the current version on September 30, 2026. Check the managed policies page for a later version before you attach one.

Policy What it grants Who gets it
AWSTransformCustomExecuteTransformations Run existing transformations, and read and update campaigns The pipeline execution role
AWSTransformCustomManageTransformations The preceding, plus authoring: upload and delete transformation packages, manage knowledge items, tag resources The people or pipelines that author definitions
AWSTransformCustomFullAccess Both of the preceding, reasonable for a first run but wider than a governed setup needs Nobody, after setup is complete

The execute-only policy also carries its own iam:CreateServiceLinkedRole statement scoped to the AWS Transform service-linked role, so a first run works without AWSTransformCustomFullAccess even in a fresh account.

Give the pipeline execution role the execute-only policy. Authoring stays with whoever maintains the transformation definitions, so a build step holding that role is not authorized to rewrite the definition that it’s about to run:

aws iam attach-role-policy \
  --role-name my-transform-ci-role \
  --policy-arn arn:aws:iam::aws:policy/AWSTransformCustomExecuteTransformations

Then confirm what the identity holds, rather than what you meant to give it:

aws iam list-attached-role-policies --role-name my-transform-ci-role
aws sts get-caller-identity

The execute-only policy still lets the role run whatever is in the registry. For transformations you publish yourself, you can narrow it further, because transformation packages are AWS resources with Amazon Resource Names (ARNs). The transform-custom:ExecuteTransformation action takes a package resource and supports the aws:ResourceTag condition key. Tag the packages you publish, and scope the role to the tag:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "RunOnlyApprovedTransformations",
      "Effect": "Allow",
      "Action": "transform-custom:ExecuteTransformation",
      "Resource": "arn:aws:transform-custom:us-east-1:111122223333:package/*",
      "Condition": {
        "StringEquals": { "aws:ResourceTag/Approved": "true" }
      }
    },
    {
      "Sid": "ConversationApiCannotBeScopedToAResource",
      "Effect": "Allow",
      "Action": "transform-custom:ConverseStream",
      "Resource": "*"
    }
  ]
}

We confirmed the mechanics with the IAM policy simulator: it allows the execute call when the tag is present and returns an implicit deny when the tag is absent. Note the scope of the example, though. The Resource element names packages in your account, and an AWS managed package such as AWS/java-version-upgrade is not a resource in your account, so a role holding only this policy can’t run it. The execute-only managed policy itself grants ExecuteTransformation on Resource "*" for that reason. The practical split is tag-scoping for the definitions you publish, alongside the managed policy’s broader grant for the AWS managed catalog.

The second statement is the limit of this approach. The actions table in the service authorization reference lists no resource type and no condition keys for ConverseStream, so that statement stays at "*". A role scoped to one transformation can still open a conversation with the service.

The broader limit is that IAM bounds the API surface and not the edits. The execute-only policy still authorizes whatever the transformation does to your working tree, because the scope of changes inside a repository is a local filesystem question. That’s what Control 1 is for on a laptop, and what code review of the resulting branch is for in a pipeline.

Control 3: Cap what a run can spend

Now think about the same upgrade from the cost perspective. The fleet-wide upgrade from the opening scenario means a few hundred runs, but you’re constrained by your budget. With the spend control, you can bound the agent minutes used by each run and use the account-level budget to watch cumulative spend across the fleet.

Start with what an agent minute measures. You’re billed per agent minute, but an agent minute is not a wall-clock minute. The two match only while a single agent is running, and AWS Transform can run several agents at once on one task, so each of them accrues its own minutes. Per the AWS Transform pricing page, minutes accrue while the service is actively planning, reasoning, analyzing, or modifying code, and not while it waits on local compilation, tests, or builds.

AWS Cost Explorer recorded 54.0 agent minutes for the discovery run (CLI 3.9.0) and 45.8 agent minutes for the clean-clone confirmation run (CLI 3.10.0). At the published rate of USD $0.035 per agent minute, the confirmation run cost approximately USD $1.60. A 30-agent-minute cap would therefore stop this particular upgrade partway through, which makes it a useful review checkpoint rather than a budget sized for uninterrupted completion. In practice, the number is experimental. It depends on your budget for the task, and it can take multiple iterations at a smaller scale first to get an understanding of the agent minutes your tasks need.

Set the per-run cap with --limit:

atx custom def exec -n AWS/java-version-upgrade -p . \
  -c "JAVA_HOME=$(/usr/libexec/java_home -v 1.8) mvn clean verify" \
  --limit 30

We wanted to see what the CLI does when it hits the cap, so we ran the same transformation non-interactively on a scratch clone with --limit 1. On AWS Transform CLI 3.10.0 the run stopped at 1.09 agent minutes, printed Budget limit reached: 1.09 / 1.00 Agent Minutes. Exiting., and returned process exit code 2. The repository was left clean. The exit code is the part we were checking. The web documentation describes the message and the resume path but not an exit code. The CLI --help output on 3.10.0 does state that the run exits with code 2 when the limit is reached, and the run confirmed it. It’s also what turns spend into control flow, because a pipeline can tell a budget stop from a transformation failure. The 0.09-minute overshoot comes from how coarsely the CLI receives usage events, so treat the limit as a close bound rather than an exact fractional-minute cutoff.

The work is preserved. Review the partial result, then resume the same conversation with a higher cap. The trust settings file from Control 1 still governs the resumed interactive session:

atx --conversation-id <conversation-id> --limit 60

A pipeline can handle the bounded stop separately:

status=$?
if [ "$status" -eq 2 ]; then
  echo "Agent-minute budget reached; review before resuming"
  exit 0
elif [ "$status" -ne 0 ]; then
  echo "Transformation failed"
  exit 1
fi

Entering /usage during an interactive session reports the accumulated agent minutes without ending the conversation. Minutes persist across an interruption, so when you resume a session after Ctrl+C, the count continues rather than starting over. Per the pricing page, an interrupted transformation can be resumed for up to 24 hours before it’s stopped automatically. We did not test the expiry, so treat that window as documented rather than measured.

The per-run cap says nothing about how many runs start, so back it with an account-level AWS Budgets budget. A budget like the one in this sample sends a notification and does not stop runs, so the next run still starts after the alert. AWS Budgets also updates its data up to 3 times a day, which means a fleet of runs can pass the threshold before the alert arrives. To stop new runs at a threshold, you can configure a budget action that applies an IAM policy or a service control policy (SCP), which this post does not cover.

The sample repository ships a monthly budget of USD $50 filtered to the AWS Transform service, with an alert at 80 percent of actual spend:

aws budgets create-budget \
  --account-id "$(aws sts get-caller-identity --query Account --output text)" \
  --budget file://guardrails/budget.json \
  --notifications-with-subscribers file://guardrails/notifications.json

Edit the email address in guardrails/notifications.json before running that command, because the file ships a placeholder. To see where the month stands, query AWS Cost Explorer for the same service dimension:

aws ce get-cost-and-usage \
  --time-period Start=$(date -u +%Y-%m-01),End=$(date -u +%Y-%m-%d) \
  --granularity MONTHLY --metrics UnblendedCost \
  --filter '{"Dimensions":{"Key":"SERVICE","Values":["AWS Transform"]}}'

Use the cap to bound each run, and use the budget to find out when the runs together cost more than you planned.

Limits of these controls

Retention is a separate question from permissions. The --do-not-learn flag opts one execution out of lesson extraction for that transformation definition. A different control governs whether your content is used for service improvement, and you configure it as an AI services opt-out policy in AWS Organizations. Neither one changes the fact that the transformation sends code to the service to do the work. Both govern what happens to it afterward.

Retained lessons also reach back into later runs. In the verification run, the agent diffed against a baseline commit carried in from a previous run on the same repository, so a README that we had added by hand between the two runs appeared in the diff, and the validation summary credited the transformation with adding it. Lessons persist repository-specific facts across runs, and on a repository that changes between runs they can misattribute human work to the agent.

The number of commits a run produces is not fixed. The same transformation on the same repository produced two commits on the first run and a single commit on each of the next two, and the commits covered a different set of plan steps each time. Review the result branch with git log and git diff rather than assuming one commit per plan step.

Clean up

The controls in this post are configuration rather than provisioned infrastructure, so there is little to tear down. Delete the budget when you no longer want the alert:

aws budgets delete-budget \
  --account-id "$(aws sts get-caller-identity --query Account --output text)" \
  --budget-name aws-transform-monthly

The trust settings file at ~/.aws/atx/trust-settings.yaml and the policy attachments on the execution role are yours to keep or remove. If you also deployed the sample application to AWS to see the upgraded service running, run npx cdk destroy from the infra/ directory. One artifact survives by design. The container image stays in the AWS Cloud Development Kit (AWS CDK) bootstrap repository in Amazon Elastic Container Registry (Amazon ECR), because cdk destroy does not remove bootstrap assets. Storage for it costs about a cent a month, and deleting the image from the cdk-*-container-assets-* repository clears it. Leaving the sample deployed costs roughly USD $13 a month, so tear it down when you’re finished with it.

Conclusion and next steps

You can now put a few hundred upgrades on a schedule, because the execution identity and the agent-minute cap apply to each run wherever it executes. A trust settings allowlist adds a third control on a developer machine. That allowlist took the interactive sample run from 23 approval prompts to zero without blanket tool trust. We built the execution identity from the AWS Transform managed policies in IAM and narrowed it to approved transformations with a resource tag. An AWS Budgets budget and alert on the account backed the agent-minute cap on each run. Because every measurement came from running AWS/java-version-upgrade against the sample repository, you can repeat the runs in your own account before you decide what to change. We ran them in August and September 2026, with the confirmation runs on AWS Transform CLI 3.10.0.

The configuration leaves room to grow. If you publish your own transformation definitions, tag them and scope the execute permission to the tag, which holds a pipeline role to the transformations you have reviewed. And if you run transformations over proprietary repositories, the --do-not-learn flag covers a single execution. An artificial intelligence services opt-out policy in AWS Organizations covers service improvement across the account.

To go further, see the AWS Transform custom user guide for the execution modes and the trust settings reference, the AWS managed policies for AWS Transform for the identity split, and the service authorization reference when you scope a policy to specific transformations. Pricing and the definition of an agent minute are on the AWS Transform pricing page. The complete source code is available in the sample repository.

For more on what these transformations do, see AWS Transform custom: AI-driven Java modernization to reduce tech debt, Automating AWS SDK for Java v1 to v2 upgrades with AWS Transform, and Migrating your Java applications to AWS Graviton using AWS Transform custom.

 


About the authors

Hamza Alfarrash

Hamza Alfarrash

Hamza is an Associate Delivery Consultant with AWS Professional Services on the Application Modernization team. He applies AI to application modernization and helps insurance services customers bring legacy applications to AWS.

Salman Moghal

Salman Moghal

Salman is a Principal Consultant at AWS Professional Services Canada, specializing in agentic AI and cloud-native architecture. He architects and builds the AI systems that modernize operations across banking, insurance, and financial services.

[$] SUSE’s efforts to disable setuid system-wide

Post Syndicated from jzb original https://lwn.net/Articles/1100257/

Setuid (and setgid) binaries have long been a source of headaches, and security
vulnerabilities, on Linux systems. They are largely seen as something to be
avoided; SUSE’s security team, for example, forbids
the inclusion of setuid or setgid binaries in packages
unless they have been
reviewed and explicitly allowed. Thorsten Kukuk wants to go a step further and
disallow them entirely. At the 2026 All Systems Go! conference, he spoke
about his work to disable setuid system-wide, as well as some of the hurdles
that remain.

Building an AEO scoring pipeline with AWS Lambda durable functions

Post Syndicated from Nick Beall original https://aws.amazon.com/blogs/compute/building-an-aeo-scoring-pipeline-with-aws-lambda-durable-functions/

When an AI agent crawls your documentation looking for answers, it doesn’t browse like a human. It scores structure, extracts entities, checks factual consistency, and moves on, often in under a second. If your content doesn’t surface clearly in those evaluations, it won’t appear in AI-generated answers regardless of its current search ranking. This problem is called Agent Engine Optimization (AEO), and solving it requires running multiple evaluation steps that are expensive, stateful, and failure-prone.

In this post, you build a five-step AEO scoring pipeline using AWS Lambda durable functions. Each step (fetch, score, analyze, publish, record) runs as a checkpointed operation that suspends without incurring additional costs between stages, retries without re-spending on prior work, and reads like ordinary Python. You score content against five AEO signals, see a real audit example, and walk away with a deployable AWS Serverless Application Model (AWS SAM) template.

A note on fetching: The pipeline uses a plain HTTP GET, mimicking how non-rendering AI crawlers (GPTBot, ClaudeBot, PerplexityBot) see pages today. Sites that rely on client-side JavaScript may score lower because most AI agents do not execute JavaScript.

Solution overview

The pipeline runs as a single Lambda durable function that chains five checkpointed steps. Scoring is fully deterministic (pure Python, no large language model (LLM)), so the same page consistently produces the same score. Amazon Bedrock generates prose analysis explaining the results. For production deployments that surface generated content to end users, consider adding Amazon Bedrock Guardrails for responsible AI controls. Because the function suspends between steps without incurring additional costs, a failure at step 3 resumes from step 3. The deterministic scores from step 2 are already checkpointed.

The following architecture diagram shows the five pipeline stages:

  1. Fetch – Retrieve the target page through an HTTP GET and extract structured signals (headings, token count, robots.txt rules, llms.txt, AGENTS.md).
  2. Score – Apply the deterministic rubric: threshold-based checks produce a reproducible 0–100 score with no LLM involvement.
  3. Analyze – Send the scores and raw signals to Amazon Bedrock, which generates prose findings and fix recommendations (temperature 0 for consistency).
  4. Publish – Render the audit report as HTML and JSON, write to Amazon Simple Storage Service (Amazon S3).
  5. Record – Persist the score and metadata to Amazon DynamoDB for trend tracking.
Architecture diagram of the AEO scoring pipeline showing five checkpointed Lambda durable function steps in sequence: fetch, score, analyze with Amazon Bedrock, publish to Amazon S3, and record to Amazon DynamoDB.

Figure 1: The five checkpointed stages of the AEO scoring pipeline, from fetch through record.

This gives you retry safety, cost predictability, and simpler debugging. The pipeline runs fully automated by default, with an optional callback gate for workflows requiring human approval.

Why Lambda durable functions

An AEO audit is five operations that depend on each other, any of which can fail mid-flight. Without checkpointing, a retry means re-running every step from the beginning, including Bedrock calls that already returned valid results.

Lambda durable functions solve this with checkpointed steps. Each step’s output is persisted before the next begins. When a Bedrock call gets throttled at step 3, only step 3 retries. The pipeline doesn’t re-run steps 1 and 2 or re-spend on their inference tokens.

Between steps, the function suspends with no environment held open, so you pay nothing while waiting. The pipeline reads as a single Python file, top to bottom. Each step’s checkpoint is independently inspectable, so debugging a failed run means looking at one step’s result rather than replaying the full execution log.

Prerequisites

A SAM template in the GitHub repository provisions the DynamoDB table, S3 bucket, Lambda durable function, and required IAM roles. You deploy the full stack with sam build && sam deploy --guided.

Estimated time: 20–30 minutes. Each audit run uses a single Bedrock inference call plus brief Lambda durable function and DynamoDB usage. See the AWS Pricing Calculator for estimates based on your Region and usage volume.

The five AEO signals

Scoring evaluates content against five signals drawn from Addy Osmani’s Agent Engine Optimization framework. Each signal maps to a capability an AI agent needs when deciding whether to use your content. Scoring is deterministic: every point maps to a measurable threshold (file exists, token count under ceiling, heading hierarchy valid). No LLM is involved in producing the number.

1. Discoverability (0–25)

This signal checks whether the URL returns server-rendered HTML, whether an llms.txt file exists at the domain root, and whether AI crawlers receive a successful response. A page returning HTTP 200 with parseable content passes. A page returning 403 or 429 to AI user agents fails.

2. Parsability (0–25)

This signal scores heading hierarchy (H1 through H6), presence of code blocks and tables, and whether the page has semantic landmarks. Because agents parse structurally rather than linearly, it’s essential for sites to be well organized and logical. Osmani suggests serving Markdown and HTML, leading each section with the outcome, and using tables for parameter references, among other tips that make your site easier to parse.

3. Token efficiency (0–25)

It measures total token count against a 30,000-token ceiling and a 15,000-token quick-start target. Pages bloated with navigation elements or repeated boilerplate score lower.

4. Capability signaling (0–15)

This signal checks for AGENTS.md or skill.md files describing available actions, required inputs, authentication, and rate limits. Without these, agents discover capabilities through trial and error.

5. Access control (0–10)

It checks robots.txt for AI-specific rules (GPTBot, ClaudeBot, PerplexityBot). Pages with explicit Allow or Disallow rules for AI user agents score higher than pages with no AI-specific rules, where access is open by default. Either choice is fine. The signal rewards making the decision on purpose.

Deterministic scores feed into the analyze step, where Bedrock generates prose explanations and actionable fix recommendations.

Example: auditing a real page

To see the pipeline in action, here are the results from a technical documentation page: a conceptual overview for a cloud computing service.

Target: A technical documentation page (conceptual overview, 958 words)

Overall score: 58/100

Signal Score Key finding
Discoverability 17/25 Server-rendered, llms.txt exists (but bloated at 295 KB / ~74K tokens compared to the recommended 5K token limit). No AGENTS.md.
Parsability 14/25 Invalid heading hierarchy (H1 to H6 to H2). No fenced code blocks or tables despite being technical documentation. 63.9% HTML noise ratio.
Token efficiency 25/25 1,245 tokens. Well under both the 15K quickstart target and 30K ceiling.
Capability signaling 0/15 No skill.md. Agents cannot discover what actions this documentation enables.
Access control 2/10 No AI-specific rules in robots.txt. Access is accidental rather than intentional.

Top three fixes:

  1. Create a skill.md declaring what agents can do with this service (available actions, required inputs, constraints).
  2. Fix the heading hierarchy to follow valid nesting (H1, H2, H3) and add fenced code blocks with language tags.
  3. Add an AGENTS.md at the domain root and explicit AI crawler rules to robots.txt.

The scoring is deterministic, and every point maps to a measurable condition. Amazon Bedrock then runs LLM inference on the results to generate the prose findings.

Under the hood: Steps and callbacks

Every step in the preceding audit, from fetch through record, runs as a checkpointed step. The callback is optional and sits after the analyze step, if you want a human to review the report before it’s published.

Lambda durable functions expose two core primitives: steps and callbacks. This pipeline runs fully automated by default (five steps, no human gate), but you can opt into a callback-based approval gate when the use case calls for it.

Steps

A step is a unit of work. The runtime checkpoints its return value before the next step begins. You declare one with the @step decorator:

@durable_step
def score_content(step_context: StepContext, page_content: str) -> dict:
    response = bedrock.invoke_model(
        modelId=os.environ["BEDROCK_MODEL_ID"],
        body=json.dumps({"prompt": build_scoring_prompt(page_content)})
    )
    return json.loads(response["body"].read())

If this step succeeds, its return value is persisted. On retry, completed steps are skipped and their cached results passed forward. Two rules:

  • Keep return values small. Return structured results, not raw API responses.
  • One side effect per step. A step that calls Bedrock and also writes to DynamoDB is harder to reason about on retry. Make one external call per step.

Callbacks

By default, the pipeline runs all five steps without stopping. To add a human review gate, turn on the callback. The function suspends without incurring additional costs until an external signal arrives. In practice, you wire this to a Slack bot or approval dashboard rather than a CLI command. Resumption happens through the SendDurableExecutionCallbackSuccess AWS API call with the callback ID. Because this is an authenticated API call, only principals with the appropriate IAM permissions can signal completion.

# In the handler (called on DurableContext, not inside a step):
callback = context.create_callback(name="review_report")
send_to_reviewer(callback.callback_id, report)

# Execution suspends here until the external system calls
# SendDurableExecutionCallbackSuccess with the callback ID.
result = callback.result()

The function suspends at callback.result() without incurring additional costs. The external system calls SendDurableExecutionCallbackSuccess with the callback ID and the approval payload. Timeouts raise TimeoutError for fallback routing.

When to add a callback

Run fully automated when… Add a callback when…
Scheduled audits or continuous integration and continuous delivery (CI/CD) regression monitoring High-stakes pages needing human sign-off
Reports feed directly into dashboards or alerts External workflow integration (Slack, Jira, webhook)
Fixes are suggestions, not auto-applied actions Downstream actions are gated (auto-filing tickets)

By default, the pipeline skips the callback entirely. To turn it on, set APPROVAL_GATE=true in the function’s environment variables.

Clean up

To avoid ongoing charges, delete the stack when you’re done experimenting:

sam delete --stack-name aeo-scoring-pipeline

This removes the Lambda durable function, DynamoDB table, S3 bucket (if empty), and the associated IAM roles. If your S3 bucket contains audit reports you want to keep, download them first or remove the DeletionPolicy: Delete setting from the template before deploying.

Conclusion

You now have a five-step AEO scoring pipeline in a single Lambda function that checkpoints between steps without incurring additional costs. Some steps involve LLM inference you don’t want to pay for twice. Durable checkpoints help make sure you do not.

To get started:

  1. Clone the repository: github.com/aws-samples/sample-aeo-scoring-pipeline
  2. Deploy with sam build && sam deploy --guided
  3. Run your first audit: aws lambda invoke --function-name AeoScoringPipeline --payload '{"url": "https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html"}'

Next steps: schedule audits using Amazon EventBridge, add Slack alerts when scores drop, or extend the rubric with domain-specific signals.

For more on Lambda durable functions, see the AWS Lambda durable functions Developer Guide. For the AEO framework, see Addy Osmani’s original post on agent-optimized content.

Introducing Amazon ECS Gateway: A fully-managed ingress solution for ECS services

Post Syndicated from Channy Yun (윤석찬) original https://aws.amazon.com/blogs/aws/introducing-amazon-ecs-gateway-a-fully-managed-ingress-solution-for-ecs-services/

When platform teams manage containerized applications at scale on Amazon Elastic Container Service (Amazon ECS), configuring ingress networking for each service often becomes a bottleneck. For every new service, teams must provision and connect Application Load Balancers (ALBs), AWS Certificate Manager (ACM) certificates, AWS WAF web ACLs, and security groups, then keep those resources in sync as services scale, update, or operate. This per-service overhead slows down deployment velocity and increases the risk of configuration drift across an organization.

Today, we’re announcing Amazon ECS Gateway, a new account-level resource that simplifies ingress networking for containerized workloads by dynamically provisioning and managing the necessary infrastructure on your behalf, with all resources created directly in your account.

ECS Gateway introduces a clear separation of responsibilities between platform teams and application teams.

  • Platform teams create and configure a gateway, defining organizational networking standards including subnets, security groups, TLS certificates, listener settings, and WAF web ACLs, in a single place. They then associate the gateway with one or more ECS clusters, making it available to services running on those clusters.
  • Application teams opt their services in by adding a gateway connector parameter when creating or updating a service. ECS provisions the required per-service resources automatically: an ACM certificate, a unique DNS hostname, listener rules, target groups, and a service security group that permits inbound traffic only from the ALB. No networking expertise required.

ECS Gateway offers host-based routing using ALBs and enables up to 25 services to share a single ALB, optimizing infrastructure costs. ECS manages the full lifecycle of the underlying infrastructure, provisioning the first ALB when your first service connects, adding more ALBs as capacity is reached, and removing them when no longer needed. Each service receives a unique DNS hostname, giving your teams a ready-to-use endpoint the moment provisioning completes.

A first look at ECS Gateway

In the ECS console, platform team can create a gateway and sets its configuration. The infrastructure role is an IAM role that ECS assumes to provision and manage networking resources such as ALBs, security groups, and certificates in your account. To create a gateway, provide a gateway name and select an infrastructure role. You can optionally associate the gateway with ECS clusters during creation or later.

Choose a load balancing scheme, either public or VPC-internal traffic routing. Under Additional Settings, you can also set up a listener, port, maximum count of load balancers the gateway can scale to, and a web ACL lets you protect your web applications. Some settings, including the name, scheme, VPC, subnets, IP address type, listener protocol and port cannot be changed after gateway creation.

Using AWS Command Line Interface (AWS CLI), call CreateGateway with a name (unique per account), an infrastructure role, and a configuration object. No infrastructure is provisioned until a service connects with a gateway connector.

aws ecs create-gateway \
  --name my-team-gateway \
  --infrastructure-role-arn arn:aws:iam::123456789012:role/ECSGatewayRole \
  --configuration '{
    "HostSharedAlb": {
      "networkConfiguration": {
        "subnets": [
          "subnet-080779f9bd887757a",
          "subnet-03d5b2fead6bc6258",
          "subnet-0861210a397d31252",
          "subnet-06539e194023cb77a"
        ],
        "ipAddressType": "ipv4"
      },
      "scheme": "internet-facing"
    }
  }'

Once the gateway is created, you can take a look at the detailed configuration, gateway routes, load balancers, and associated clusters.

Now associate the gateway with one or more clusters (many-to-many), to make it available to ECS services on those clusters.

Use AssociateGatewayWithCluster to make the gateway available to services on a cluster:

aws ecs associate-gateway-with-cluster \ 
--gateway my-team-gateway \ 
--cluster arn:aws:ecs:us-east-1:123456789012:cluster/my-cluster

To create and manage the ECS Gateway programmatically, including calling APIs and searching documentation, try the AWS MCP Server and plugins with your preferred AI coding tool. To learn more, visit the Amazon ECS Gateway documentation.

ECS Gateway:Behind the scenes

A gateway can be associated with up to 10,000 clusters, and a single cluster supports up to five gateway associations. This many-to-many relationship gives platform teams the flexibility to share a gateway across environments or assign multiple gateways (for example, public and private) to a single cluster.

Platform teams can control access by choosing which gateways to associate with which clusters. For additional control, use IAM policies to restrict who can deploy services to specific clusters.

Application teams add the gateway connector to each service definition in CreateService or UpdateService. If you have an existing ECS service with its own load balancer, you can adopt ECS Gateway incrementally by calling UpdateService to add a gateway connector. Both ingress paths remain available, allowing you to validate the gateway connection before removing the existing load balancer configuration.

aws ecs create-service \ 
  --cluster my-cluster \ 
  --service my-api-service \ 
  --task-definition my-api-service:1 \ 
  --gateway-connectors '[{ "portName": "api", "filters": {"accessTypes": ["PUBLIC"]}, "healthCheck": {...} }]'

At deployment time, Amazon ECS evaluates the gateways associated with the cluster and connects the service to every compatible gateway. A gateway is compatible when its access type matches the service’s connector filter, it is in the same VPC as the service, and it covers all Availability Zones used by the service’s configured subnets.

For each connection, Amazon ECS assigns the service to an ALB with available capacity and provisions the per-service resources: an ACM certificate, a unique DNS hostname, a listener rule routing traffic for that hostname to the service target group, and a service security group allowing inbound traffic only from the ALB security group. If no existing ALB has capacity, ECS creates a new one (up to maxAlbCount referring to quota limit). Once provisioning completes, the gateway route becomes ACTIVE, and the service URL is ready to receive traffic.

ECS Gateway handles common operational scenarios automatically:

  • Redeployments: On each deployment, ECS re-evaluates gateway associations. If new compatible gateways have been associated since the last deployment, your service connects to them. If a gateway has been disassociated, your service disconnects and the route is removed. Your service always reflects the current state of the cluster gateways at deployment time.
  • Service deletion: ECS automatically disconnects the service from every gateway, removes all gateway routes, and cleans up all per-service resources. If the service held the last route on an ALB, that ALB is deprovisioned.

Things to Know

Here are a few important details to keep in mind:

  • Networking: ECS Gateway is available for ECS services using the awsvpc network mode across all launch types (Amazon ECS EC2, AWS Fargate, and ECS Managed Instances). Your service Availability Zones (AZs) must be a subset of each gateway’s AZs on the cluster. A gateway ALB only routes traffic to tasks in AZs where it has subnets. We recommend creating gateways and services for a given cluster with the same set of AZs.
  • Capacity: If maxAlbCount is reached and no ALB has available capacity, the deployment fails. Default quota limit of maxAlbCount is 10. Plan your maxAlbCount setting according to the number of services you expect to connect.
  • Observability: Use ALB metrics in CloudWatch with the TargetGroup dimension to monitor per-service traffic, and LoadBalancer dimension to monitor traffic across the shared ALB. Use describe-target-health to check real-time target status. Configure CloudWatch alarms for metrics such as HealthyHostCount, TargetResponseTime, and HTTPCode_Target_5XX_Count, using gateway ARN-based tagging so monitoring scales automatically with your maxAlbCount.

Now available

Amazon ECS Gateway is available now in all commercial AWS Regions where ECS is available at no additional charge. You pay only for the underlying infrastructure provisioned in your account when services connect to a gateway, including applicable ALB charges, CloudWatch logs and metrics, ACM certificates, and data transfer charges. To learn more, visit the Amazon ECS Pricing page.

To get started, create your first gateway today in the ECS console, and send feedback to AWS re:Post for ECS or through your usual AWS Support contacts.

— Channy

[$] Discussing a rewrite of XArray

Post Syndicated from daroc original https://lwn.net/Articles/1100520/

The work to support block sizes larger than the page size on XFS has been
difficult. Many users of XFS run stable kernels, which means that they aren’t testing
new XFS code as it is released. That has made Daniel Gomez wonder whether
adopting Rust for some of the data structures underlying XFS, particularly

XArrays
, could reduce the number of bugs lurking in the code. At
Kangrejos 2026, he led a discussion about the potential problems with converting
XArray to Rust. The assembled kernel developers had a number of reasons to be
cautious about replacing the XArray code, but Gomez was determined to experiment
with the possibility, citing both security and performance improvements.

eMudhra intermediate CA compromise

Post Syndicated from jzb original https://lwn.net/Articles/1101643/

Mozilla has announced
that it has received a report that private keys associated with eight eMudhra intermediate certificates have
been compromised:

These keys could allow attackers to issue fraudulent HTTPS certificates for
websites they do not control. An attacker who could intercept a user’s
connection could use those certificates to impersonate websites and potentially
read or modify traffic.

Mozilla has blocked the affected certificates using its OneCRL
centralized list of revoked certificates, and has updated the trusted
certificate list in Network
Security Services (NSS) 3.131.1
.

There is a single-page
site
about the compromise that goes into more detail. According to the site
the certificates have also been blocked in Chrome since October 17, Apple
devices since the 19th, and Microsoft blocked the certificates on the
20th. There is also a table that displays whether a browser or other client
still trusts the compromised certificates.

The NetBSD foundation has issued
instructions
to block the certificates on NetBSD installations. Users of
other browsers and operating systems may wish to check for updates or guidance
to disable the compromised certificates.

България между член 4 и страха от Русия

Post Syndicated from Искрен Иванов original https://www.toest.bg/bulgariiya-mezhdu-chlen-4-i-straha-ot-rusiya/

Член 4
Страните по Договора ще се консултират съвместно всеки път, когато по мнение на някоя от тях е възникнала заплаха за териториалната цялост, политическата независимост или сигурността на която и да е от тях.

Член 5
Страните по Договора се договарят, че въоръжено нападение, предприето срещу една или повече от тях, в Европа или в Северна Америка, ще се разглежда като нападение срещу всички тях, вследствие на което те се договарят, че в случай на такова въоръжено нападение всяка от тях, упражнявайки правото си на индивидуална или колективна самоотбрана, признато в член 51 от Устава на Организацията на обединените нации, ще окаже помощ на нападнатата страна или страни по Договора, като незабавно предприеме, индивидуално и съгласувано с останалите страни по Договора, такива действия, каквито смята за необходими, включително употребата на въоръжена сила, за възстановяване и поддържане на сигурността в Северноатлантическата зона.

(Северноатлантически договор, Вашингтон, 4 април 1949 г.)

България между член 4 и страха от Русия

Военните конфликти, в които е участвала България през своята най-нова история, винаги са били предшествани от поляризация в общественото мнение. Достатъчно е да си припомним разделението, което цари сред българския елит и избирателите в навечерието на двете световни войни, както и отчаяните опити на българските монарси да лавират между силните на деня в Европа. Някой би нарекъл тези исторически събития специфични само за българското житие, но ако погледнем историята на международните отношения ще видим, че не е така. 

Въпреки че войната взема много човешки жертви и води до тотално разрушение, може би една от най-фаталните последици от нея е слагането на край на солидарността между хората. Всъщност смело можем да кажем, че днес България дори няма нужда да влиза в нов военен конфликт, тъй като тук вече се води вътрешна война, а изходът от нея ще е решаващ за дългосрочното бъдеще на нашата страна. 

Но какво тогава трябва да ни притеснява в случващото се съвсем близо до нашите териториални води? 

Ами ако САЩ излязат от НАТО?

Живеем в свят, в който съюзите не са даденост. И това донякъде е добре, защото бяхме склонни да ги приемаме за вечни, а е полезно да си припомняме защо съществуват и какво точно изчезва, когато ги няма. Но ако САЩ се оттеглят от НАТО, в какво ще се превърне Европа и на каква цена? От Искрен Иванов.

Защо сега и защо Черно море?

Войната в Украйна продължава вече четири години, а през цялото това време нашата морска граница изглеждаше неестествено спокойна. Анексията на Крим през 2014 г. и последвалата ескалация на конфликта останаха някак си далечни за българина, който обикновено посрещаше новините за ставащото с отегчение от факта, че постоянно го занимават с едно и също, с недоверие, че войната може да стигне до България, или с неоправдана истерия, че страната ни ще стане жертва на амбициите на Запада Русия да бъде победена на всяка цена. 

Тези наративи бяха същинското проявление на войната у нас, тъй като в началните ѝ етапи Москва нямаше интерес да я изнася извън границите на Украйна. Реално целта на Русия в тази война е Америка, но пътят към нея минава през Украйна – една руска победа би принудила Вашингтон отново да приеме Москва като глобален актьор в международната система. САЩ обаче декларираха категоричната си подкрепа за България и Румъния, а Европа започна усилено да се въоръжава. Истинската война на Стария континент се водеше на когнитивно ниво – в интернет, в социалните мрежи, дори и в медиите.

Нещата започнаха да се променят след 28 февруари тази година, когато САЩ нанесоха първите си въздушни удари срещу Иран – преломен политически момент, който за пореден път показа, че Вашингтон разглежда ядрената програма на Техеран като екзистенциална заплаха за сигурността си. По това време Украйна вече разполагаше с най-силната армия от дронове в Европа, докато Русия се опитваше да допълни конвенционалната си стратегия с ядрено говорене. В същото време присъединяването на Финландия и Швеция към НАТО лиши Русия от стратегическото ѝ превъзходство в Калининградска област, а Полша даде да се разбере, че е готова да посрещне решително всяко руско нападение срещу границите ѝ. 

Така Черно море остана единствената уязвима точка, в която Кремъл можеше директно да провокира Алианса. Тази опасност не беше отчетена навреме нито от България, нито от останалите страни в региона, тъй като падането на иранския режим изглеждаше въпрос на време, а подобен развой би отслабил стратегическите позиции на Русия и би освободил американски ресурс за възпиране на Москва. Оказа се обаче, че Иран има технологии и стратегия, с която да воюва, и това сякаш даде кураж на Русия да продължи да предизвиква НАТО.

Основната причина за уж закъснялото желание на Русия да изнесе конфликта се появи, когато руските загуби на фронта рязко се повишиха, след като Киев започна все по-широко да прилага асиметрична стратегия, основана на масовото използване на дронове. Нещо повече, Кремъл имаше нужда конфликтът да излезе извън рамките на руско-украинската война, за да легитимира собствената си теза, че истинският сблъсък е със САЩ и НАТО. Това би могло да стане по два начина. 

Първият е директен военен конфликт между Вашингтон и Пекин за Тайван – сценарий, на който Русия, изглежда, много се надяваше, но той така и не се реализира. Подобен конфликт би отворил втори фронт на глобалното противопоставяне със САЩ и би отклонил американски ресурси и внимание от войната в Украйна. За Китай обаче това би означавало да рискува собствения си възход в името на руския стратегически интерес.

Това постави руските политици в особено положение – те или трябваше да признаят, че в продължение на четири години Русия не може да победи Украйна, или все пак да търсят как да изнесат конфликта извън украинските граници, с което да покажат на партньорите си, че войната с Киев е част от американския план за унижението на руснаците.

Как би започнала войната в Черно море?

Първото и основно условие за пламването на конфликт в Черно море парадоксално са действията на самото НАТО, или по-конкретно стъпките, които Алиансът ще предприеме, за да отговори на инцидентите с нападнатите кораби в басейна. Тук следва да отбележим, че макар в официалната позиция на българското правителство да не се назовава по име държавата, която стои зад тези атаки, случващото се недвусмислено е заплаха за националната ни сигурност. И не само защото е в териториалните ни води, а защото задълбочава разделението в обществото, което съществува от началото на войната в Украйна. Всъщност управляващите вече няколко пъти намекнаха, че всички доказателства сочат в полза на Русия, макар че самата Русия отрича.

Страдателният залог на генерал-премиера

„Онзи-който-не-бива-да-се-назовава“ продължава да потапя кораби в Черно море. Междувременно Румъния сваля дронове и укрепва отбраната си, а ние търсим доказателства и призоваваме безадресно за мораториуми. Колко струва снишаването, когато войната вече е на брега ни? От Емилия Милчева.

Ситуацията е класическа провокация към страна членка на НАТО. Такива провокации суперсилите са предприемали по време на Студената война, за да проверят какви ще бъдат действията на отсрещната страна. Или по-просто казано – хвърлена ръкавица. Затова и най-недалновидното, което Алиансът би могъл да направи, е да не демонстрира решителност. 

За щастие, в Европа вече се чуха множество гласове в подкрепа на страната ни, а България, Румъния и Турция вече обсъждат задълбочаване на тристранното си сътрудничество за сигурността и безопасността на корабоплаването в Черно море. До голяма степен това изчерпва и прагматичните стъпки, които бихме могли да предприемем на този етап, макар в публичното пространство все повече да се дискутира задействането на член 4 от Северноатлантическия договор.

Може ли обаче член 4 може да влоши нещата? 

Механизмът на съюзническите консултации, залегнал в член 4, е доста стар и може да служи като последна политическа бариера пред ескалация, която би поставила на дневен ред член 5. Тук идва и обективната реалност – Русия иска точно това. Атаките с дронове ще продължат, докато член 4 не бъде задействан, което ще даде основание на Москва да започне подготовка за потенциално нахлуване в Европа просто защото нито руският президент, нито неговите генерали ще чакат задействането на члена за колективната отбрана. 

САЩ са свръхфокусирани върху войната с Иран и възпирането на Китай и макар техните войски все още да са разположени в Европа, реалната тежест за отбраната на Стария континент пада върху самите европейци. Затова и един потенциален конфликт между Русия и Алианса в Черно море би могъл да започне именно след задействането на член 4 или ако някой от съюзниците директно отговори на провокациите (освен ако договорката между Тръмп и Путин за руския дизел не се окаже толкова обещаваща, че да разубеди руснаците във „виновността“ на Европа).

Войната, която никой не иска, но всички вече водят

За американската дилема между Израел и глобалната икономика, за иранската ядрена амбиция и за новото разделение между Европа и САЩ. Какво ни показва блокадата на Ормузкия проток? От Искрен Иванов.

Ролята на САЩ в конфликта с Русия

Основният предупредителен сигнал за евентуална война в Европа би бил зачестяването на атаките с дронове в Черно море, а той вече е налице. Големият проблем е, че ако НАТО задейства член 4, Москва може да използва това като аргумент пред Тръмп, че Европа, а не Русия тласка Алианса към война.

За Кремъл подобно внушение не противоречи на стратегическото му противопоставяне със САЩ – напротив, то би могло да отслаби трансатлантическата връзка и да остави европейските съюзници без пълната подкрепа на Вашингтон. Ролята на Америка ще е решаваща при евентуален опит на Русия да нападне европейските държави – ако това се случи по време на междинните избори, може да бъде използвано от привържениците на Тръмп като аргумент Вашингтон да не се въвлича в още една война. 

Изглежда, че американският президент е сред малкото лидери, които разполагат с реални инструменти да повлияят върху сметките на Кремъл за евентуална ескалация в Европа. Сделката между Тръмп и Путин за руския дизел би могла да даде на Москва допълнителен икономически стимул да се въздържа от подобни провокации, но руснаците едва ли ще се трогнат от това.

Да, някой би нарекъл този опит изключително циничен, защото предложението Европа отново да купува руски газ я връща в прегръдката на Москва, докато зависимостта ѝ от американските енергийни доставки остава. Но и Тръмп не би се трогнал от това, тъй като е доста обиден на някои европейски държави, че не го подкрепиха във войната с Иран.

Накъде след „Мюнхен 2026“? Отворените въпроси пред Европа и България

Мюнхенската конференция по сигурността затвърди усещането (де да беше само такова), че старият трансатлантически баланс се разпада. Вече всички сме на една вълна по този въпрос. Но докато Европа търси автономия, България рискува да остане между линиите на разделението. От Александър Малинов.

В същото време Москва не трябва да си прави илюзии, че САЩ ще гледат безучастно какво се случва в Европа. В най-лошия случай ще предприемат политика на офшорно балансиране, отваряйки по-широк фронт за възпиране на Русия на територията на НАТО. В публични изказвания на високопоставени военни от Алианса се допуска при евентуална руска атака той да нанася бързи удари по военни цели на руска територия, включително в района на Санкт Петербург, при избягване на ядрена ескалация.

Стратегическият проблем е, че Алиансът никога не е бил предвиден да напада или да воюва срещу СССР, а да защитава Европа от него. И ако след 11 септември беше ясно, че елитните части на НАТО могат да се справят с „Ал Кайда“, когато срещу тях стои ядрена сила, сценариите могат лесно да се размият. 

Най-решаващата променлива за изход от ескалацията обаче остава ядреният фактор, който на този етап е и най-успокояващият. Докато Русия говори как ще изпепели цяла Европа, едва ли Европа има от какво да се страхува – това е остаряла реторика от съветско време, която може да е в състояние да разбуни духовете в обществото, но сама по себе си не означава непосредствена ядрена заплаха за Алианса. Реалната употреба на ядрено оръжие срещу страна от НАТО би създала риск от ответен удар и ескалация с катастрофални последици и за самата Русия.

Когато обаче подобно говорене отстъпи място на зачестили провокации от страна на Москва, това би могло да е сигнал, че Русия се готви да изнесе конфликта извън границите на Украйна. Сред факторите, които ограничават подобна ескалация, остава способността на САЩ бързо да съсредоточават военни сили там, където възниква заплаха, включително чрез самолетоносачите си, разположени в Близкия изток. Един от тях е атомният самолетоносач „Джордж Х. У. Буш“, чието Седмо палубно авиокрило разполага със значителни възможности за нанасяне на удари по наземни цели. Затова за Москва би било по-изгодно да избягва пряка провокация срещу Алианса и да остави НАТО да направи следващия ход.

Ролята на България и реалната опасност от война

Може би най-сигурният сценарий, при който Русия няма да бави започването на война срещу европейците и би действала решително срещу Алианса, е, ако САЩ започнат сухопътна операция срещу Иран. Тогава американските стратегически ресурси ще са толкова ангажирани с военните действия в гигантската държава, че едва ли ще успеят да се намесят пълноценно в Европа, освен с настоящото си военно присъствие на Стария континент, което и без това е силно редуцирано.

Една голяма стратегическа грешка, която европейците допуснаха, е, че ако иранският режим беше свален по-рано, южният фланг на Русия отдавна щеше да бъде оголен и Москва щеше да възприеме малко по-различен подход. За нещастие, това така и не се случи, като нежеланието на Европа да се намеси не трябва да се търси само в една посока – както вече пояснихме в един от предишните материали.

В останалите сценарии всичко зависи от реакциите на НАТО на следващите провокации, а отговорът на България ще е особено важен за целостта на Алианса. В това отношение новите многостранни формати в Черно море са обнадеждаващи. 

НАТО, ЕС и „българската мечта“. Но кое по-напред?

Представите за „българската мечта“ често се люшкат между исторически митове и заемки от чужди модели. Но как изобщо се ражда тази идея и защо днес изглежда толкова трудно да я формулираме? Нейните корени, трансформации и рискове в съвременния геополитически контекст проследява Искрен Иванов.

Липсата на противоракетна система и противодронова защита обаче крият сериозни рискове от инцидент, което от своя страна се корени в погрешната стратегия, по която се модернизираха въоръжените сили на страната ни. Тази модернизация беше завършена някак си на парче, а крайъгълният камък на всяка подобна реформа е придобиването на способности, с които една държава да се защитава от ракети и дронове, следвано от обновяване на военния авиопарк и флота. Затова и до осигуряването на такива ресурси съюзническата подкрепа от съседите ни ще има водеща роля, а присъединяването ни към Европейската инициатива за защита от дронове е повече от закъсняло.

И накрая, нека не забравяме, че прибързаното активиране на член 4 и член 5, както и необузданите призиви НАТО да използва „цялата си мощ“ срещу Русия са проява на същата истерия, която наблюдаваме и в онзи сегмент от обществото ни, който мечтае за завръщането на добрия стар СССР. 

Реалностите обаче са други – въпреки стратегическата надпревара и санкциите САЩ и Русия продължават да имат пресечни икономически и енергийни интереси. 

Отношенията с Китай и Иран заемат далеч по-важно място в дневния ред на американската президентска администрация и това може да се промени най-рано след междинните избори. Ето защо решението да бъде въведен мораториум върху военните удари срещу цивилните и търговските кораби в Черно море и засилването на натовското присъствие в черноморския регион са може би най-адекватната политика, която страните в региона могат да следват на този етап. Така, ако един ден се стигне и до задействането на член 4 или член 5, България ще е изчерпала възможностите за деескалация, преди да потърси съюзническа защита, а за Москва ще бъде далеч по-трудно да представи този избор като поредното българско „предателство“ спрямо Русия и да задълбочи още повече разделението в българското общество.

Заглавно изображение: Военнослужещи и техника на НАТО по време на учението Steadfast Dart 2026, което тества способността на Алианса за бързо разполагане и подсилване на силите му. Снимка: НАТО

Security updates for Tuesday

Post Syndicated from jzb original https://lwn.net/Articles/1101617/

Security updates have been issued by AlmaLinux (environment-modules, kernel, pcs, perl-URI, python3.14-urllib3, and webkit2gtk3), Debian (python-django), Fedora (bind, bind-dyndb-ldap, chromium, cifs-utils, curl, haveged, python3.15, qt5-qtbase, rust-rbw, rust-routinator, rust-rustls, xorg-x11-server, and xorg-x11-server-Xwayland), Mageia (firefox, firefox-l10n, nss, flatpak-builder, imagemagick, libpng, libxi, libxpm, libxtst, openssh, postgis, and thunderbird), Oracle (emacs, environment-modules, libsoup, perl-URI, python3.14-urllib3, webkit2gtk3, and xorg-x11-server), Red Hat (go-fdo-client, go-fdo-server, golang, kernel, kernel-rt, openssh, postgresql:16, postgresql:18, and rust-sequoia-sq), SUSE (alsa, AusweisApp, chromedriver, ffmpeg-4, kubernetes, libsoup2, nvidia-open-driver-G07-signed, p11-kit, perl-Net-OAuth, perl-Net-Whois-Raw, pgvector-devel, pi-coding-agent, podman, python, python3, python3-base, python3-doc, python315, python36, thunderbird, and velociraptor), and Ubuntu (cifs-utils, curl, golang-1.22, libxtst, openssh, openssh-gssapi, and php8.5, php8.3, php8.1, php7.4, php7.2, php7.0, php5).

Introducing tsrs: a Port of TypeScript to Rust

Post Syndicated from Boshen Chen original https://blog.cloudflare.com/typescript-rust-port/

Cloudflare and VoidZero are now working with Max Schwenk of Owner.com on tsrs, a Rust port of the TypeScript compiler. We are doing this work in close collaboration with the TypeScript team, and incubating tsrs as part of the Oxc JavaScript compiler toolchain.

Our goal is to help developers spend less time waiting for their code to be checked and more time building. Making TypeScript faster and more memory-efficient also helps bring complete development environments to more places, including right in your browser.

To be clear, we are not forking TypeScript. We believe the TypeScript team should remain in full control of the language’s design and direction. They have maintained a long-term vision while guiding TypeScript’s adoption across the JavaScript community. We do not want to change that.

The TypeScript team can move quite fast on many things, but for some features they may spend years intentionally exploring options and making prototypes before picking the right implementation. A Rust port has to follow those decisions closely, or it will eventually fall apart and become incompatible. We have to preserve the TypeScript language strictly based on its original implementation – and the TypeScript team should remain solely responsible for deciding what that looks like.

Performance and memory improvements

Early benchmarks of our Rust port of TypeScript are promising: tsrs is up to 8x faster and uses ~70% less memory compared to TypeScript 7.0 when using the same number of type check workers:

As you can see, the next version of TypeScript already brings many performance improvements, making TypeScript 7.1 beta up to 2.5x faster than TypeScript 7.0. Some of these optimizations were upstreamed from tsrs, and we’re hoping to find more.

You may have seen recent benchmarks of different TypeScript ports with various performance claims, often due to using larger machine types and different options compared to TypeScript’s defaults. We worked with the TypeScript team to ensure the above benchmarks are comparing the same configuration.

By default, tsrs ships with an adaptive configuration dependent on the machine type that uses more memory but is even faster:

Try tsrs

TypeScript in Rust aims to be a faithful port of TypeScript. You can try an experimental release by installing the tsrs package from npm:

npm install tsrs
npx tsrs

Why now?

We’ve seen many attempts at porting TypeScript to other languages over the years. Because of the complexity involved, only one of the ports succeeded: the TypeScript team’s port of TypeScript from JavaScript to Go.

However, frontier AI models can now do large-scale ports almost independently in a matter of days. We have now seen slopforks, faithful ports, and completely re-architected versions of TypeScript in Rust or C++. There is clearly a demand for even faster TypeScript.

Why Rust?

You might be thinking: “TypeScript’s Go port already made TypeScript 10x faster and far more memory efficient than the previous version written in TypeScript. Why isn’t that enough?”

However, it’s not just about speed. Rust offers some unique advantages: running in WebAssembly. The Rust + Wasm story has always been strong, and one of the reasons comes down to the fundamentals of Rust’s language design. When you compile a Go program to Wasm, you have to compile Go’s garbage collector along with it. But Rust’s memory management model enables straightforward memory interactions between a WebAssembly guest and host. This is one of the key things that makes Rust a much better fit for the WebAssembly use-case: memory efficiency.

Cloudflare Workers have supported Wasm for a long time. Just a few weeks ago we announced supporting native Rust in Workers with the new Emscripten target for wasm-bindgen. When you run entire software build and deployment systems in tight loops on Workers, the benefits of a small memory footprint pay off quickly. It means you can run these workloads so efficiently that it’s now possible to run fully encapsulated development systems in places where they couldn’t have been run before.

This enables new possibilities when combined with agents: You are now able to run a development toolchain end-to-end in a fully sandboxed environment on Cloudflare. But with Wasm, you can also run those environments safely client-side in your browser, or on an embedded device, or an IoT application.

How will the tsrs port be maintained?

First, we are working with Theo Browne to merge tsc-rs and tsrs into a single package that supports all of TypeScript’s features including Language Server Protocol (LSP) and content mappers. Second, we’ve seen a lot of success with how we maintain Vinext in sync with its upstream, Next.js. We explained how we do this in a recent blog post:

Each morning, an agent reviews the changes, fetches diffs, and opens tracking issues for anything that could affect Vinext. Every night, the compatibility matrix is regenerated as we run the Next.js test suite against Vinext. When one of these tests or issues reveals a gap, agents are now in the position where they can identify the change across both codebases, build a reproduction, port any relevant tests, and propose a fix.

We will apply the same process to TypeScript, taking extreme care that we don’t introduce diverging behaviors from upstream TypeScript. When we come across performance improvements, we’ll attempt to contribute them back to the TypeScript Go codebase so everyone wins.

Finally, we recognize that there are some differences to the upstream version of TypeScript right now, like for example the order in which diagnostics are printed. We aim to reduce those differences to a minimum by the time we publish a stable release.

What’s next?

Cloudflare is already the home of many free and open source Rust tools that power the JavaScript ecosystem, such as Vite, Rolldown, Oxlint, and Oxfmt. With more than 10 million developers building on Cloudflare, we care a lot about making the underlying developer toolchain faster, more portable, and easier to run in more places. We are excited to experiment with deeper tsrs integrations into our stack, for example for type-aware lint rules in Oxlint.

Like with Vinext, our goal is to run tsrs as a well-maintained healthy open source project — without getting in the way of TypeScript itself. The Oxc team has already begun refactoring the codebase to make it faster, use less memory, and make it maintainable. We know the TypeScript team is taking this seriously – they are experimenting with memory and performance improvements for tsc. We are excited to collaborate with the TypeScript team and the JavaScript community to build the best and fastest implementations of TypeScript together.

The next Chair of the Board of Trustees, Blythe Masters

Post Syndicated from Philip Colligan, CBE original https://www.raspberrypi.org/blog/the-next-chair-of-the-board-of-trustees-blythe-masters/

I am delighted to share the news that Blythe Masters has been appointed as the next Chair of the Raspberry Pi Foundation’s Board of Trustees. Blythe brings a wealth of experience to our governance at a time when our mission to enable all young people to realise their full potential through the power of computing and AI has never felt more urgent.

Blythe Masters
Blythe Masters

Blythe is one of the most prominent figures in global financial services and fintech. A graduate and Senior Scholar of Trinity College, University of Cambridge, she spent nearly three decades at JPMorgan, rising to its Executive Committee and holding senior leadership roles including Chief Financial Officer of the Investment Bank and Head of Global Commodities. After leaving JPMorgan in 2014, Blythe served as CEO of Digital Asset and later became a Founding Partner at Motive Partners, the fintech-focused investment firm. Most recently, Blythe served as Group CEO of FNZ, the global wealth technology platform and Motive Partners portfolio company.

Blythe has extensive corporate governance experience, currently serving as the Chair of JPMorgan Securities plc and non-executive director on the board of SymphonyAI. She has previously served on numerous other boards including A.P. Moeller Maersk, Credit Suisse, Santander Consumer USA, Forge, and GCM Grosvenor.

Blythe also has significant experience in nonprofit governance, having served as the co-chair of the Global Fund for Women, vice chair of ID2020, chair of the NYC chapter of Susan G. Komen Breast Cancer Foundation, chair of the Hyperledger Foundation, and numerous other trustee roles.

Blythe takes over the Chair from Sir John Lazar who has led the Board of Trustees since 2020. Under Sir John’s leadership the Foundation has supported hundreds of thousands of educators and tens of millions of students all over the world to develop the skills, knowledge, and mindsets to shape their world through computing and AI.

Sir John Lazar
Sir John Lazar

We have achieved so much under his leadership and I have learned a huge amount from working with him. We are all profoundly grateful to Sir John for his contribution. 

Blythe will take up the role at the end of March 2027 and John will be staying on to manage the transition and handover. While it is hard to imagine a world where John isn’t our Chair, I am genuinely excited about the prospect of working with Blythe.

What is a Board of Trustees?

As a charity, the Foundation is governed by a Board of Trustees that is responsible for ensuring we use our resources effectively to advance our charitable goals. We are fortunate to have an exceptionally strong and diverse group of Trustees who bring significant experience and expertise from a wide range of backgrounds. Trustees generally serve two three-year terms. Trustees are volunteers who provide their time and expertise to the Foundation for no remuneration. 

You can see the full list of Trustees and Members on our website.

The post The next Chair of the Board of Trustees, Blythe Masters appeared first on Raspberry Pi Foundation.

Possible new Cellebrite iPhone Radio Zero-Day

Post Syndicated from Bruce Schneier original https://www.schneier.com/blog/archives/2026/10/possible-new-cellebrite-iphone-radio-zero-day.html

Justin.sh is reporting on a confidential Cellebrite document that has been leaked to him:

Cellebrite calls it Advanced Unlocks (AU from here on), not to be confused with the “advanced unlocking services” they’ve marketed for years. A press release from earlier this year confirms AU is a new product for 2026.

[…]

According to Cellebrite, AU:

  • is a “next-generation AFU vector that is more resilient to Apple’s OS changes”
  • works with Stolen Device Protection enabled, which tightens USB restrictions
  • is enabled by a dedicated appliance with a stated range (!) of about 8 meters.

AU = Advanced Unlocks, Cellebrite’s term. AFU = after first unlock.

Cutting our inference bill by 68% by being obsessive about optimization

Post Syndicated from Ryan Skidmore original https://blog.cloudflare.com/obsessive-inference-optimization/

When we previously blogged about our AI code reviewer in April, an average review cost a palatable $1.19. By the end of August, that figure had shot up to about $2.46. We hadn't made any significant architectural changes: the coordinator was still handing work to the same set of specialist reviewers, and the same risk tiers decided how intensive of a review we gave.

Around mid-September we tasked our agents with working out where those tokens were going and cutting them — aggressively. With code quality at high effort, the average review now costs just $0.77, down 68% from the $2.43 it averaged over our two most expensive weeks before we started. Our main goal was to cut costs, but as part of this work we also managed to optimize the reviewer, so that the median review now takes just 1m 32s instead of the 4m 09s it took before.

About 40% of the savings came from changes and optimizations to the reviewer's code, and the rest from experimenting with which models we use and how much effort they put in.

How a review doubled in cost

The chart below shows the cost of an average review on each weekday since March. It holds the mix of review tiers fixed, so a day with more large reviews doesn't skew the data.

The cost per review steadily rose from $1.22 (March) → $1.44 (June) → $1.74 (July) and finally, to $2.32 (August). A small part of that is that more reviews landed in the most expensive tier (59% of reviews in March and then 65% in August). We believe there’s a simple answer for why: models are getting much better, and much more capable. Naturally, engineers are producing bigger, more complex changes which means more of them fall into our “full review” tier. We found that most of the cost increase actually came from model changes over those months, though.

At the start of August we moved every reviewer onto Anthropic models, which raised the cost per job from $1.38 to $2.03. We made this choice because we found that during our evals, Anthropic models performed better on this specific workload than other models.

Our internal standards reviewer, Codex, ended up on Claude Opus 4.7 due to a mix of performance and reliability. That took it from about $0.25 per review to $0.88 in August, which made it more expensive than the coordinator agent itself.

The cache hit rate also increased, going from 85.7% in April to 97% by September, while the number of tokens per review more than tripled, from 0.91 million to 2.99 million. A high hit rate on three times as many tokens still costs a lot more, even with the low cache read cost.

What else has changed since our last post?

One of the biggest changes is that the reviewer now runs on OpenCode v2, which is a ground-up rewrite from v1. Before switching all reviews over from v1 to v2, we ran every review in every canary repo on both v1 and v2 in a comparative shadow mode. The two agreed on the approval decision in 98% of the merge requests we tested, so we moved everyone over in one go in early October and removed the v1 code. Comparing two days with the same models, full-tier reviews on v2 finish about 13% faster. We’re also currently experimenting with other ways to run our internal AI reviewer, including solely on Durable Objects using pi-durable. We’re hopefully going to have more on this coming soon, so keep an eye out!

So, where were the tokens going?

Right before we started focusing on cost optimization, our AI review spend was split by: output tokens making up about 23%,  cache writes cost 43%, and cache reads cost 34%. This means that  most of the money was going on context the model had already seen or was about to see again.

That makes complete sense when you look into a single session. The code quality reviewer made 16.6 large language model (LLM) calls per review on average and over those calls it wrote 72k tokens to the cache and read back 1.08m, because each call sends the whole conversation as context. A session's cost grows with both the number of calls and the size of the context, and over the last few months we had slowly increased both.

It’s worth taking a few minutes to read about how Anthropic's prompt cache works. It works by caching parts of the prompt that have already been computed by the large language model (LLM). These are known as prefixes, because they only work on the messages at the start of the conversation, not mid way through or the end. This means that a future request can reuse an earlier prefix, up to the part of the conversation that changes from what’s been cached. In our case, the harness tools, system prompt and messages at the start of our AI reviewer sessions rarely change, so they’re a great candidate for prefix caching. A prefix is only stored where a cache breakpoint ends it. Writing to the cache costs 1.25 times the normal input price and reading costs 0.1 times, and an entry expires after five minutes unless something reads it.

That means we had two things to work on: make the first request of each session read from the cache instead of writing to it, and make each session use fewer calls.

Sharing a cached prefix between every repository

Beginning September 14, the reviewer recorded the token usage of each reviewer's first LLM call, plus a hash of the first system block exactly as it was sent to the provider.

The data showed that the first request of a session found a cached prefix only 14% to 26% of the time. Every session starts with the same tool definitions and the same agent prompt, between 11k and 21k tokens that are identical in every repository we review, so almost all of them should have been cache hits.

The problem ended up being in how OpenCode v1 constructed the system prompt: It built one block containing our agent prompt, followed by an environment section with the working directory and the date, and placed the only cache breakpoint at the end of that block. The shared part was never stored on its own, so a review in one repository could never reuse what a reviewer ran in another repository had written.

On September 16, we added a marker to the end of every agent prompt and a plugin hook that split the block in two at that marker. The first half now ended on bytes that every job shares, and got its own breakpoint. To keep it that way we had to remove anything repository-specific from in front of it, so the list of available skills is disabled and tool descriptions can't change between jobs.

The same day we also moved our internal standards (Codex) reviewer's reference index (a table of internal standards, a skill file and a list of reference files) into its prompt. Until then every session had spent its first three turns reading those files with tool calls. Its LLM calls per run decreased from 15.9 to 9.6, and its cost per run dropped from $1.12 to $0.72.

Together these took the cost per review from $2.47 down to $2.02, and the drop was noticeable as soon as we kicked off the release.

Fewer calls, fewer dollars

The next set of changes went out on September 21 and 23. None of them were particularly impressive by themselves, but together they took about $0.20 off every review.

Change

What was happening

Effect of change

Removing tools that reviewers couldn't use

Sub-reviewers could see the tool that posts the final review comment. In 57% to 76% of sessions they tried to call it, were rejected by a check on the server, and spent another call recovering.

One wasted call gone, and about 2.9k fewer tokens of tool definitions in every sub-reviewer request

Reading files in parallel

In 64% of the code quality reviewer's turns, the model made exactly one tool call, usually to read a single file.

Reviewers read the shared context and all of their patches in their first response, up to 150 KB

Skipping reviewers on re-reviews

About two-thirds of our jobs are a second or later review of the same merge request, and often three of the reviewers have nothing new to look at.

On full-tier re-reviews, AGENTS.md reviews are skipped 56% of the time, performance reviews skipped 23% and documentation reviews skipped 13%.

Stopping after the review is posted

OpenCode passed the result back to the model for one more turn, which read the entire conversation again to write a summary nobody saw.

About $0.07 saved per job

Treating a slow reviewer differently from a broken provider

A reviewer that hit its time limit was treated like a provider failure and started again with the same time limit.

Together with longer time limits and lower effort, security reviewer timeouts went from 1.8% of runs to almost none

Canceling canceled jobs

GitLab's Kubernetes executor doesn't send a signal to a job's script when the job is cancelled. In a sample of 446 cancelled jobs, 358 kept running until the review was finished.

Jobs check their own status every 15 seconds and stop if cancelled

The most involved change was sharing one prefix between three reviewers. The code quality, security, and performance reviewers now send identical first requests with the same tools, a shared rules file, then the merge request with every patch included. Each reviewer's own instructions follow after that, and we make sure that one of them writes the cache and the other two read from it.

A cache entry can't be read until the response that wrote it has started streaming, and the three reviewers start at the same time. So the second and third wait for the first one's response to begin, for up to 20 seconds, before sending their own first request. It slows them down slightly and saves about nine cents per reviewer.

Changing models and effort

The next chart shows the same line from the start of September, with each change marked.

When we split the period at every release and every configuration change, the code changes turn out to be the smaller part of the saving.

Code changes account for $0.65 of the $1.67, and model and effort changes for $1.06. Each step compares the weekday hours before a change with the hours after it, so this isn't a statistically sound controlled experiment.

The coordinator had been running three model and effort combinations since August. On the same model, full-tier jobs on medium effort finished 20% sooner, so we dropped the other two. In a later randomized split, Opus 5.5 at medium showed no measurable change in review outcomes or critical findings compared with Opus 5, so we switched. Opus 5.5 at high effort cost more than Opus 5.5 at medium while not producing a meaningful increase in findings.

Opus 5.5 is cheaper per token than Opus 5, but price is only responsible for about $0.20 of the drop. The rest comes from sending and generating fewer tokens. Since the start of September cache reads per review are down 82%, cache writes down 54%, and output down 67%.

The most recent change was a new model. On October 3, we split every Sonnet reviewer 50/50 between Sonnet 5 and Sonnet 5.5. After two and a half days, Sonnet 5.5 was 62% cheaper per run for code quality, 58% for performance and 47% for security. The two models cost the same per token, so all of that comes from using fewer tokens. On full-tier reviews, the 5.5 reviewers posted about as many critical and warning findings as the Sonnet 5 ones, so on October 5 we moved all of them to 5.5.

On Sonnet 5.5 at medium effort, the code quality reviewer makes 1.7 LLM calls per run instead of 6.2, and more than half of its runs finish in a single call, which means they don't open any other file in the repository. At high effort it makes 3.4 calls per run. We were concerned that this made reviews shallower, which we cover in the quality section below.

To see whether more effort brings findings back on the larger tiers, the code quality reviewer has been split 50/50 between medium and high effort on 5.5 since October 5. After one day, high effort costs 61% more per run and posts 58% more critical and warning findings. That's about two cents per review, and the quality section below shows those extra findings are worth having, so we're moving code quality to high effort. The cost figures in this post assume that.

Latency

We weren't aiming to make reviews faster, but fewer calls also means less waiting, which means faster reviews and happier engineers.

The slowest reviews improved the most. In the four weeks before September 14, 37.9% of reviews took longer than five minutes and now just 0.7% do. The 99th percentile today is faster than three quarters of reviews were in April. Every tier got faster along the way with the median full-tier review going from 5m 16s to 1m 54s, lite from 2m 58s to 1m 14s, and trivial from 77 seconds to 30.

By August, the median review was 14% slower than what we reported in April and the 99th percentile 48% slower, so a good part of this work just got us back to where we had been.

Review quality

A cheaper review isn't worth much if it also finds fewer problems, and the first chart here looks like that's what happened.

With code quality on Sonnet 5.5 at high effort, it posts 40% fewer critical and warning findings than it did in late August. Security and performance, which stay at medium effort, post 56% and 71% fewer. A fall in severe findings doesn't tell you on its own whether real problems are being missed or noise is being cut, so we looked at it from two directions.

First, we checked whether authors act on what the reviewer flags. For samples of critical and warning findings, we compared the commit that was reviewed with the author's next push, and counted a finding as acted on if the lines it pointed at had changed. Lines 30 to 40 away from each finding changed about 15% of the time in every period, which gives a baseline for edits that happen by coincidence.

Before the changes, authors acted on 34% of the code quality reviewer's serious findings. Now it's 59%. The reviewer flags fewer things, but those it flags are much more likely to lead to a change, so the number of findings that led to a change per 1,000 reviews is about the same. We now saw 132 against 145 before, which is within the margin of error. Across all four reviewers, serious findings that led to a change went from 229 to 321 per 1,000 reviews.

Second, we had two models from different providers judge a blinded sample of 657 findings from the three Sonnet reviewers, with the diff each one referred to and without knowing which period it came from. Disagreements went to a third model. Each finding was labelled important, minor, duplicate, wrong, or unverifiable, and its severity was marked as fair, too high, or too low.

Most of what disappeared was minor findings, plus some duplicates and findings the judges couldn't verify from the diff. Important findings per review went from 0.28 before to 0.30 now, and the intervals overlap. The bigger change was in severity. Before, the judges rated 0.46 critical or warning findings per review as more severe than they deserved. Now that's 0.14, a 70% drop and the clearest result in the research. Much of the drop in critical findings is the reviewer no longer calling minor things critical.

So the answer depends on the reviewer. For code quality at high effort, what disappeared was mostly noise and inflated severity, and it still finds about as many real problems as before. Medium effort did lose real findings. In the randomized split, code quality at medium led to roughly a quarter as many acted-on findings as at high, which is why we're moving it to high. Performance also lost real findings: the judges found about 64% fewer important performance issues per review, which in practice is about one fewer important finding every 24 full-tier reviews. Security fell by about a quarter, though that isn't statistically significant. Both are still at medium effort, and testing them at high is next.

Neither method is perfect. An author changing the flagged lines shows they did something, not that the finding was right. The judges are models too, and there are only 35 to 66 findings in each group. The "now" window is also only a day long. Even so, the two methods agree with each other, and neither suggests that code quality is finding fewer real problems.

Reliability has held up as well. Reviews that end without a verdict went from 0.79% to 0.27%, and security reviewer retries from 3.5% to 0.04%.

Where this leaves us

An average review now costs $0.77 instead of $2.43, and the median one finishes in 1m 32s instead of 4m 9s. The biggest single saving came from checking what actually reached the provider on each session’s first request, and finding a way to order those messages in a way that was as consistent as possible across commits, merge requests and repositories. The remaining cost savings came from relentlessly experimenting with different models and effort mixes, while making sure we put any preconceived bias for particular models aside.

Lower cost reviews didn’t automatically mean worse quality, but it didn’t automatically mean fine either. Code quality at high effort finds about as many real problems as before, with much less noise and far fewer inflated severities. Performance lost real findings at medium effort, and security may have, too, so testing both at high effort is next. If you run agents at any kind of volume, the two things we’d check first are whether each session’s first request actually reads from the cache, and whether your reviewers still find real problems at the effort level you’re paying for.

If you’re at Cloudflare Connect in San Francisco, I’m talking about how we built the reviewer instead of buying one in my session, It’s time to build, not buy, on Tuesday, October 20 at 12:30 PM PDT. I’ll also be in the Speaker Connection Lounge from 1:10 PM if you want to ask about any of the numbers in this post.

The collective thoughts of the interwebz