Modern software systems are increasingly automated. We automate deployments, infrastructure changes, scaling, incident response, and data pipelines.

And now, with AI agents, we are starting to automate operational decisions too. That sounds like progress, but it creates a problem that is easy to miss:

We are getting better at executing operations without necessarily getting better at specifying what those operations are supposed to achieve.

A deployment pipeline can run successfully and still violate an important business constraint.

An infrastructure script can finish without error and still leave the system in the wrong state.

An AI agent can complete a sequence of actions and still produce an outcome that nobody can verify afterward.

In many systems, operational intent still lives across:

runbooks
tickets
Slack messages
CI/CD configuration
Terraform files
dashboards
monitoring rules
human memory

Those artifacts are useful. But they are not the same thing as an executable operational specification.

An executable operational specification describes what should happen in a way that can later be evaluated against what actually happened.

That distinction becomes increasingly important as execution gets faster, more distributed, and more autonomous.

In this article, I'll explore:

  • why automation alone is not enough,

  • the difference between execution logic and operational intent,

  • what an executable operational specification is,

  • why observability does not solve this problem by itself,

  • how specifications can make operational behavior verifiable,

  • how this applies to CI/CD, infrastructure, incident response, and AI agents,

  • what a minimal specification might look like,

  • and what problems this approach still does not solve.

The central idea is simple:

If a system can execute an operation automatically, we should also be able to describe what successful execution means independently of the tool performing it.

Prerequisites

You should be comfortable with:

  • software architecture

  • CI/CD

  • infrastructure automation

  • observability

  • distributed systems

  • basic testing concepts

  • automation workflows

You do not need to use any particular cloud provider, deployment platform, or orchestration tool.

The ideas in this article are deliberately tool-independent.

Table of Contents

Automation Solves Execution, Not Intent

Consider a deployment pipeline. It might:

build the application
run tests
build an image
push the image
deploy it
wait for readiness
mark the pipeline as successful

If every step completes, the pipeline turns green. But what did the pipeline actually prove?

Usually, something like:

the configured steps completed successfully

That is useful. But it is not necessarily the same as:

the intended operational outcome was achieved

Suppose the deployment succeeds technically, but:

the wrong image version was deployed
only two replicas are running instead of three
the error rate increased
a required feature flag is disabled
the service is healthy but cannot reach a dependency
the rollout violated a regional constraint

The executor did its job. The operation still failed in a broader sense. This is the gap between execution success and operational conformance.

Automation tells us:

The steps ran.

What we often need to know is:

Did the resulting system satisfy the intended conditions?

Those are different questions.

Operational Knowledge Is Usually Fragmented

Most production systems already contain operational knowledge. The problem is that it is scattered.

For example, a deployment rule might exist partly in:

GitHub Actions
Terraform
Kubernetes manifests
Grafana dashboards
PagerDuty alerts
a runbook
a ticket
a senior engineer's memory

One artifact might define how many replicas should exist. Another might define what error rate is acceptable. A third might explain when rollback is required. A fourth might describe which regions are allowed.

No single representation says:

This is the operation we intend to perform.

These are its constraints.

This is the evidence we require.

This is how we determine whether it succeeded.

That makes operations harder to reason about. It also makes automation brittle.

When the rules are distributed across tools, the executor often becomes the de facto specification.

And once that happens, it becomes difficult to ask whether the executor behaved correctly.

The logic that performs the action and the logic that defines success are effectively the same thing.

Execution Logic and Operational Intent Are Different Things

Imagine a deployment script:

kubectl set image \
  deployment/orders \
  orders=registry.example.com/orders:2026.09.19

This tells Kubernetes how to perform an action. It does not fully describe why the action is acceptable.

The operational intent might be closer to:

Deploy version 2026.09.19 of the Orders service.

Constraints:

- production must keep at least 3 available replicas
- the error rate must remain below 1%
- p95 latency must stay below 400 ms
- deployment is allowed only in us-east-1
- rollback must remain possible for 30 minutes

The command and the intent are related. But they are not the same artifact.

This distinction matters because several different executors might be able to satisfy the same intent.

For example:

Kubernetes
Nomad
a cloud deployment service
a custom deployment controller
an AI-operated platform

If the operational goal is expressed independently, the executor becomes replaceable.

If the goal is embedded inside executor-specific code, replacing the executor may also mean rediscovering the intent.

What Is an Executable Operational Specification?

An executable operational specification is a machine-readable description of an operational objective that can be evaluated against observed evidence.

At a minimum, it should answer questions like:

What are we trying to achieve?

What constraints must hold?

What evidence should be collected?

How do we determine whether the operation conforms?

For example:

operation: deploy-orders-service

target:
  service: orders
  version: 2026.09.19
  environment: production

constraints:
  minAvailableReplicas: 3
  maxErrorRate: 0.01
  maxP95LatencyMs: 400
  region: us-east-1

evidence:
  - deployedVersion
  - availableReplicas
  - errorRate
  - p95Latency
  - region

This is intentionally simple. The important part is not the YAML syntax.

The important part is that the specification defines success independently from the mechanism used to perform the deployment.

That gives us a structure like:

Operational intent
        ↓
Specification
        ↓
Executor
        ↓
Execution
        ↓
Evidence
        ↓
Evaluation

The specification becomes a stable point of reference.

A Simple Deployment Example

Suppose we want to deploy version 2026.09.19 of an Orders service.

The operation succeeds only if:

the correct version is running
at least three replicas are available
error rate stays below 1%
p95 latency stays below 400 ms

We can express that as:

type DeploymentSpec = {
  service: string;
  version: string;
  minAvailableReplicas: number;
  maxErrorRate: number;
  maxP95LatencyMs: number;
};

For example:

const spec: DeploymentSpec = {
  service: "orders",
  version: "2026.09.19",
  minAvailableReplicas: 3,
  maxErrorRate: 0.01,
  maxP95LatencyMs: 400,
};

Now suppose execution produces evidence:

type DeploymentEvidence = {
  deployedVersion: string;
  availableReplicas: number;
  errorRate: number;
  p95LatencyMs: number;
};

For example:

const evidence: DeploymentEvidence = {
  deployedVersion: "2026.09.19",
  availableReplicas: 3,
  errorRate: 0.004,
  p95LatencyMs: 280,
};

We can evaluate the evidence against the specification:

function conforms(
  spec: DeploymentSpec,
  evidence: DeploymentEvidence
): boolean {
  return (
    evidence.deployedVersion ===
      spec.version &&
    evidence.availableReplicas >=
      spec.minAvailableReplicas &&
    evidence.errorRate <=
      spec.maxErrorRate &&
    evidence.p95LatencyMs <=
      spec.maxP95LatencyMs
  );
}

Then:

console.log(
  conforms(spec, evidence)
);

returns:

true

Now imagine the pipeline technically succeeds but only two replicas remain available:

const evidence: DeploymentEvidence = {
  deployedVersion: "2026.09.19",
  availableReplicas: 2,
  errorRate: 0.004,
  p95LatencyMs: 280,
};

The executor may still report success.

The specification does not:

conforms → false

That is the distinction we want.

Turn Success Criteria into Evidence Requirements

A specification becomes useful when its claims can be evaluated.

Suppose the specification says:

error rate must remain below 1%

Then the system needs evidence for:

error rate

If it says:

at least 3 replicas must remain available

then it needs evidence for:

available replica count

This sounds obvious, but it introduces an important discipline:

Every operational requirement should imply some form of observable evidence.

For example:

Requirement Evidence
correct version deployed running image/version
minimum replicas available replica count
error rate below threshold request/error metrics
latency below threshold latency metrics
correct region runtime placement
no schema regression schema validation result

This relationship matters because vague operational goals are difficult to automate safely.

Consider:

Deploy safely.

What evidence proves that?

The statement is too ambiguous.

A better specification decomposes "safely" into conditions that can be checked.

Why Observability Alone Is Not Enough

At this point you might ask:

Isn't this just observability?

Not exactly. Observability helps answer:

What is happening?

A specification helps answer:

What should be happening?

Those are complementary questions.

A dashboard might tell you:

error rate = 1.4%

That is an observation.

But whether 1.4% is acceptable depends on an expected condition.

The specification might say:

maxErrorRate = 1%

Now you can evaluate:

observed: 1.4%
expected: <= 1%

result: non-conformant

Without the expected condition, the metric is just a number. Without the metric, the specification cannot be verified. You need both.

Conceptually:

Specification
     +
Observed evidence
     ↓
Evaluation

Specifications Make Automation Verifiable

Automation without an independent specification is difficult to verify.

Consider a script that performs:

scale service
restart pods
change routing
wait
finish

If the script is also the only place where expected outcomes are encoded, then asking whether it behaved correctly becomes circular.

You are effectively asking:

Did the automation do what the automation says it should do?

A separate specification gives you another reference point.

Now you can ask:

What was intended?

What did the executor do?

What evidence did execution produce?

Did the evidence satisfy the specification?

This makes operations easier to audit and test. It also makes failures more informative.

Instead of:

deployment failed

you can potentially say:

deployment execution completed

but specification failed because:

availableReplicas:
expected >= 3
observed = 2

That is much more useful.

Keep the Specification Independent from the Executor

One of the strongest properties of this model is executor independence.

Suppose the specification says:

Deploy Orders version 2026.09.19

Keep:
- >= 3 replicas
- error rate <= 1%
- p95 latency <= 400 ms

One executor might use Kubernetes. Another might use a managed cloud platform. Another might use a custom orchestrator.

The specification should not need to change simply because the executor changed.

Conceptually:

                 ┌── Kubernetes Executor
Specification ───┼── Cloud Executor
                 ├── Custom Executor
                 └── AI Agent

Each executor produces evidence. Each execution is evaluated against the same intent.

That gives you a useful separation:

what should happen

from:

how it happens

This separation is common in other areas of software engineering. Interfaces separate callers from implementations. SQL separates queries from storage mechanics. Desired-state systems separate target state from reconciliation logic.

Operational specifications apply a similar idea to operational workflows.

A Minimal TypeScript Model

A simple generic model might look like this:

type Constraint<T> = {
  name: string;
  evaluate(
    evidence: T
  ): boolean;
};

type OperationalSpec<T> = {
  name: string;
  constraints: Constraint<T>[];
};

For deployment evidence:

type Evidence = {
  version: string;
  replicas: number;
  errorRate: number;
};

You can define:

const deploymentSpec:
  OperationalSpec<Evidence> = {
    name: "deploy-orders",
    constraints: [
      {
        name: "correct-version",
        evaluate: (evidence) =>
          evidence.version ===
          "2026.09.19",
      },
      {
        name: "minimum-replicas",
        evaluate: (evidence) =>
          evidence.replicas >= 3,
      },
      {
        name: "error-rate",
        evaluate: (evidence) =>
          evidence.errorRate <= 0.01,
      },
    ],
  };

Then evaluate every constraint:

function evaluate<T>(
  spec: OperationalSpec<T>,
  evidence: T
) {
  return spec.constraints.map(
    (constraint) => ({
      constraint: constraint.name,
      passed:
        constraint.evaluate(
          evidence
        ),
    })
  );
}

For:

const evidence: Evidence = {
  version: "2026.09.19",
  replicas: 2,
  errorRate: 0.003,
};

you might get:

correct-version    PASS
minimum-replicas   FAIL
error-rate         PASS

That is more useful than a single generic success or failure flag. It tells you exactly which part of the intended operation did not conform.

How This Applies to CI/CD

CI/CD systems already contain some declarative elements.

For example:

steps:
  - test
  - build
  - deploy

But these steps mostly describe execution order. A specification can add operational expectations around them.

For example:

Deployment objective:
release version X

Constraints:
tests passed
artifact digest matches approved build
minimum replicas remain available
error rate stays below threshold
rollback remains possible

The pipeline still performs the work. The specification defines the conditions the pipeline must satisfy. This also makes pipeline replacement easier.

If you move from:

GitHub Actions

to:

GitLab CI

or:

Argo

the executor changes.

The operational objective does not necessarily need to.

How This Applies to Infrastructure

Infrastructure-as-code already gives us desired state. That is closely related to this idea.

For example:

resource "aws_instance" "app" {
  instance_type = "t3.medium"
}

But operational intent often extends beyond configuration state.

You may also care about:

service availability
cost limits
regional restrictions
security controls
capacity
latency
backup freshness

Those constraints may live outside the IaC definition. An operational specification can bring them together.

For example:

Provision application environment

Required:
3 instances
region = us-east-1
monthly projected cost < $500
encryption enabled
backup age < 24h

The executor may use Terraform.

The evidence may come from:

cloud APIs
cost systems
security scanners
backup metadata

The specification gives those sources a common purpose.

How This Applies to Incident Response

Incident response is another place where execution and intent are often mixed together.

A runbook might say:

restart service
clear cache
scale replicas

But the actual operational objective might be:

restore checkout availability

while:
avoiding duplicate payments
preserving order state
keeping error rate below threshold

That difference matters.

If restarting the service does not restore checkout availability, the runbook technically executed but the operation failed.

An executable specification could express recovery conditions:

checkout success rate > 99%
payment duplication = 0
queue backlog < threshold
error rate < 1%

Then incident automation can be evaluated by the result it achieves, not just the actions it performs.

How This Applies to AI Agents

This becomes even more important with AI agents. Traditional automation usually follows predefined execution logic.

An AI agent may choose the execution path dynamically. For example, an operations agent might decide to:

inspect metrics
restart a service
change capacity
modify a feature flag
reroute traffic

The exact sequence may vary from one incident to another. That makes executor-level validation harder.

You cannot always verify the agent by checking whether it followed one exact script. But you can still verify operational intent.

For example:

Objective:
restore API availability

Constraints:
do not disable authentication
do not lose queued requests
error rate < 1%
p95 latency < 500 ms
cost increase < 20%

The agent may choose different actions and the specification remains stable.

That creates a useful control structure:

Human / organizational intent
        ↓
Operational specification
        ↓
Agent
        ↓
Actions
        ↓
Evidence
        ↓
Evaluation

The more autonomous execution becomes, the more valuable this separation becomes.

What Should Go Into an Operational Specification?

A useful specification often includes several categories.

Objective

What should be achieved?

For example:

Deploy Orders service version 2026.09.19

Scope

Where does the operation apply?

For example:

environment: production
region: us-east-1
service: orders

Constraints

What must remain true?

For example:

available replicas >= 3
error rate <= 1%
p95 latency <= 400 ms

Required Evidence

What must be observed?

For example:

running version
replica count
error rate
latency

Evaluation Rules

How do we decide whether execution conforms?

For example:

version must match exactly
replicas must be >= 3
error rate must be <= 0.01

Recovery Conditions

What should happen if conformance fails?

For example:

stop rollout
restore previous route
require human approval

Not every specification needs all of these.

But separating them makes operational intent much clearer.

What Should Stay Out of the Specification?

A specification should not become another implementation script. That means avoiding unnecessary executor-specific mechanics.

For example, this is probably too implementation-specific:

run kubectl command X
wait 10 seconds
call endpoint Y
run shell command Z

Those belong in an executor. The specification should focus on the desired operational outcome.

For example:

service version = 2026.09.19
available replicas >= 3
health checks passing

A useful rule is:

If changing the execution tool forces you to rewrite the specification, the specification may contain too much implementation detail.

Some executor-specific constraints are unavoidable. But the default should be to keep intent and mechanism separate.

A Practical Workflow

If I were introducing executable operational specifications into an existing system, I would start small.

1. Pick One Important Operation

For example:

deploy service
rotate certificate
restore backup
scale worker pool

2. Write Down the Objective

Ask:

What does success actually mean?

Not:

Which commands do we run?

3. Identify Constraints

For example:

minimum availability
maximum error rate
security requirements
regional restrictions
cost limits

4. Identify Evidence

For each constraint, ask:

What observation would prove or disprove this condition?

5. Separate the Executor

Keep the mechanism that performs the work independent from the specification.

6. Evaluate After Execution

Collect evidence and compare it against the specification.

7. Report Conformance

Prefer:

3 constraints passed
1 constraint failed

over:

operation failed

8. Improve the Specification

Missing evidence and ambiguous constraints will become visible quickly.

That is useful.

The specification becomes better as operational knowledge becomes explicit.

What Executable Specifications Do Not Solve

Executable specifications are not a complete operations architecture.

They do not automatically solve:

bad requirements
incorrect metrics
missing observability
distributed transactions
security failures
poor executor implementations
organizational ownership
conflicting business goals

They also introduce their own risks:

  • A bad specification can encode the wrong objective.

  • An incomplete specification can create false confidence.

  • A stale specification can become another source of drift.

  • And not every operational decision can be reduced to a simple threshold.

Human judgment still matters. The goal is not to eliminate judgment. The goal is to make operational intent more explicit and more testable.

From Automated Operations to Verifiable Operations

Software operations have spent years becoming more automated. That trend will continue. But increasing automation creates a new question:

How do we know the automation achieved the right outcome?

Execution logs, pipeline success, and agent confidence are not enough. We need something to compare execution against. That is where an executable operational specification becomes useful.

It gives us:

intent
↓
constraints
↓
evidence requirements
↓
execution
↓
observed evidence
↓
evaluation

That structure turns an operation from:

something happened

into:

something happened,
we know what was expected,
we collected evidence,
and we can evaluate the result.

That is a much stronger foundation for automation.

Conclusion

The more software operations we automate, the more important it becomes to separate what we want from how a tool executes it.

Pipelines are executors.

Infrastructure tools, scripts, and AI agents are executors. They can all perform actions.

But the operational objective should exist independently from the mechanism carrying it out.

An executable operational specification gives us a way to describe that objective in terms of:

desired outcome
constraints
required evidence
evaluation rules

Then execution becomes something we can verify instead of merely observe.

This matters today for deployments, infrastructure, and incident response.

It will matter even more as operational systems become increasingly autonomous.

Because automation can tell us that an action was executed.

What we really need to know is whether the system ended up where it was supposed to be.