# Agentic Control Loop Architecture: Understanding Sensors, Controllers, and Actuators

> Understand agentic control loop architecture with sensors, controllers, and actuators. Learn how these components automatically maintain code quality in your codebase.

- Repository: [HumanLayer/skills](https://github.com/humanlayer/skills)
- Tags: architecture
- Published: 2026-09-12

---

**An agentic control loop treats a codebase as a dynamic system where a sensor measures current state against a set point, a controller selects the next low-risk change, and an actuator executes the change automatically to continuously maintain code quality.**

The **agentic control loop** is a framework for automating codebase maintenance by treating software repositories as dynamic systems subject to constant developer changes, dependency updates, and generated code. According to the `humanlayer/skills` repository, this architecture enables teams to deploy autonomous agents that continuously measure, decide, and act upon code quality metrics without manual intervention.

## What Is an Agentic Control Loop?

An agentic control loop is an iterative automation pattern that keeps a codebase aligned with defined quality standards. In [`plugins/design-control-loop/skills/design-control-loop/SKILL.md`](https://github.com/humanlayer/skills/blob/main/plugins/design-control-loop/skills/design-control-loop/SKILL.md), the architecture is described as a continuous cycle that counteracts system disturbances—such as accumulating TODO comments, decreasing test coverage, or obsolete imports—by repeatedly measuring the gap between current reality and desired targets, then applying targeted fixes.

Unlike traditional CI/CD pipelines that only validate changes, this loop actively generates and proposes improvements. It runs on a schedule (for example, daily at 02:00 UTC) or on-demand, creating pull requests that move the repository toward its defined **set point** one safe increment at a time.

## The Four Core Components of an Agentic Control Loop

The architecture consists of four tightly-coupled elements, each defined in [`plugins/design-control-loop/skills/design-control-loop/references/control-loop-taxonomy.md`](https://github.com/humanlayer/skills/blob/main/plugins/design-control-loop/skills/design-control-loop/references/control-loop-taxonomy.md):

### Set Point: The Target State

The **set point** defines the desired condition for a specific property of the repository. This could be quantitative ("test coverage ≥ 80%") or qualitative ("no obsolete imports remaining"). According to the design specification in [`SKILL.md`](https://github.com/humanlayer/skills/blob/main/SKILL.md), the set point is established during Phase B of the design interview and recorded in the skill's design document. It serves as the reference signal that the sensor compares against actual system state.

### Sensor: Measuring the Gap

The **sensor** is any repeatable tool that measures current repository state and computes the deviation from the set point. Implementations range from static analysis linters and AST searches to test runners and custom scripts.

As documented in the control-loop taxonomy, the sensor outputs a structured signal—typically JSON—containing measurable findings such as violation counts or file lists. A minimal implementation is provided in the repository's reference workflow, illustrating how the sensor runs first in the pipeline to capture the current baseline.

### Controller: Deciding the Next Action

The **controller** transforms sensor output into a specific, actionable change plan. This component decides *what* to act on, *how many* items to address, and *in what order*. The implementation can be deterministic (such as sorting findings by priority and selecting the first N) or fully agentic (using LLM reasoning to choose targets).

According to [`SKILL.md`](https://github.com/humanlayer/skills/blob/main/SKILL.md), the controller operates during the workflow's second phase, consuming the sensor's JSON output and producing a [`target.json`](https://github.com/humanlayer/skills/blob/main/target.json) file that specifies the exact file or violation the actuator should address. This abstraction layer ensures the actuator receives only validated, low-risk targets.

### Actuator: Executing the Change

The **actuator** is the component that physically modifies the repository. It combines a coding agent—such as Claude Code, Codex, or OpenCode—with repository-local skills that format pull requests and run validation before committing.

As specified in [`SKILL.md`](https://github.com/humanlayer/skills/blob/main/SKILL.md), the actuator reads the controller's [`target.json`](https://github.com/humanlayer/skills/blob/main/target.json) input, invokes the chosen coding agent to generate edits, executes validation scripts (like `npm test` or `go test`), and creates a pull request if checks pass. The actuator's logic lives in a generated skill directory (for example, `<skill-name>/SKILL.md`) that defines the specific editing instructions and PR templates.

## Supporting Elements: Disturbance and Memory

Two optional components enhance loop resilience:

- **Disturbance**: External factors that modify the system outside the loop's influence, such as team commits or dependency bumps. A damping mechanism—such as a PR check that compares sensor output against a baseline—can block regressions caused by these disturbances.

- **Memory**: A persistent feedback file (stored in `.github/agent-memory/<task>.md` according to [`references/memory-template.md`](https://github.com/humanlayer/skills/blob/main/references/memory-template.md)) that records reviewer feedback, false-positive exclusions, and tuning parameters for future iterations. This allows the loop to learn and adapt across runs.

## How the Agentic Control Loop Works in Practice

The loop operates iteratively through four stages, as defined in the workflow template at [`plugins/design-control-loop/skills/design-control-loop/references/workflow-template.yml`](https://github.com/humanlayer/skills/blob/main/plugins/design-control-loop/skills/design-control-loop/references/workflow-template.yml):

1. **Sensor execution** produces a measurable signal (for example, a JSON list of lint violations).

2. **Controller processing** consumes the signal, selects a subset of findings, and outputs an action plan to [`target.json`](https://github.com/humanlayer/skills/blob/main/target.json).

3. **Actuator invocation** runs the coding agent to implement the plan, validates the change, and opens a pull request with a generated body.

4. **Feedback integration** merges the PR result into the repository state, which feeds back into the next sensor run, completing the cycle.

The repository emphasizes running each piece **locally first** (Phase D) to ensure debuggability before wiring components into CI/CD (Phase E).

## Implementing the Three Components in Code

The following examples demonstrate minimal, runnable implementations derived from the repository's reference files.

### Creating the Sensor Script

A sensor that counts TODO comments and outputs structured JSON:

```bash
#!/usr/bin/env bash

# scripts/sensor.sh

set -euo pipefail

# Find all TODOs and output structured data

count=$(rg -c "TODO" .)
files=$(rg -l "TODO" . | jq -R . | jq -s .)

jq -n --argjson c "$count" --argjson f "$files" \
  '{count:$c, files:$f}'

```

This script uses `ripgrep` to scan the repository and `jq` to format the output as JSON, providing the standardized input expected by the controller.

### Building the Controller Logic

A deterministic controller that selects the first available target:

```python
#!/usr/bin/env python3

# scripts/controller.py

import json
import sys

# Load sensor output

sensor = json.load(open(sys.argv[1]))

# Pick the first file containing a TODO

target = {"file": sensor["files"][0]} if sensor["files"] else {}

# Output action plan

json.dump(target, open(sys.argv[2], "w"))

```

This Python script implements a simple selection algorithm, though production controllers might use LLM-based reasoning to prioritize complex refactoring tasks.

### Configuring the Actuator Skill

The actuator skill defines how to apply changes and create PRs:

```markdown
---
name: my-loop-actuator
description: Applies a change selected by the controller and opens a PR.
---

# Actuator Execution Steps

1. **Read input** (`target.json`) – contains the file to edit.
2. **Generate patch** using the coding agent:
   ```bash
   clcode edit --file "{{target.file}}" \
     --instruction "Remove the TODO comment"
   ```

3. **Run validation** – execute `npm test` or equivalent.
4. **If validation passes**, create a commit and push a branch.
5. **Generate PR body** using the response template and save to `pr-body.md`.

```

This Markdown file follows the specification in [`references/agent-runner-templates.md`](https://github.com/humanlayer/skills/blob/main/references/agent-runner-templates.md), defining the interface between the controller's decision and the coding agent's execution.

### Orchestrating the Workflow

The GitHub Actions workflow that orchestrates all three components:

```yaml
name: Agentic Control Loop
on:
  schedule: [{cron: "0 2 * * *"}]
  workflow_dispatch: {}

jobs:
  run-loop:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Run Sensor
        id: sensor
        run: ./scripts/sensor.sh > sensor.json

      - name: Run Controller
        id: controller
        run: ./scripts/controller.py sensor.json target.json

      - name: Run Actuator
        env:
          CLAUDE_API_KEY: ${{ secrets.CLAUDE_API_KEY }}
        run: |
          clcode run --skill ./skills/my-loop/actuator \
            --input target.json \
            --output pr-body.md

      - name: Create Pull Request
        uses: peter-evans/create-pull-request@v5
        with:
          commit-message: "Agentic loop: apply change"
          title: "🤖 Agentic update"
          body-path: pr-body.md

```

## Summary

- A **set point** defines the target state (such as test coverage thresholds or zero obsolete imports) that the loop aims to maintain.

- The **sensor** measures current repository state using tools like linters, test runners, or AST searches, outputting structured findings.

- The **controller** selects specific, low-risk targets from sensor output, deciding what the actuator should modify and in what order.

- The **actuator** executes changes using coding agents, validates results, and creates pull requests to merge improvements.

- Optional **memory** files persist feedback between iterations, while **disturbance** handlers protect against external regressions.

## Frequently Asked Questions

### What is the difference between a sensor and a controller in an agentic control loop?

The **sensor** measures and reports current state without making decisions, outputting raw data like violation counts or file lists. The **controller** consumes this data to make decisions about *which* violations to fix and *how* to prioritize them, transforming raw measurements into specific action plans. According to [`control-loop-taxonomy.md`](https://github.com/humanlayer/skills/blob/main/control-loop-taxonomy.md), the sensor answers "what is broken?" while the controller answers "what should we fix first?"

### How does the actuator ensure changes are safe before creating a PR?

The actuator runs validation scripts—such as `npm test`, `go test`, or custom lint checks—immediately after applying edits with the coding agent. As specified in [`SKILL.md`](https://github.com/humanlayer/skills/blob/main/SKILL.md), the actuator only creates a commit and opens a pull request if all validation passes. This dampening mechanism prevents the loop from introducing regressions into the main branch.

### Can an agentic control loop handle external disturbances like dependency updates?

Yes, the architecture accounts for **disturbances**—external factors like team commits or dependency bumps that modify the system outside the loop's control. The repository recommends implementing a dampener, such as a PR check that compares sensor output against a baseline before allowing the actuator to merge changes. This ensures disturbances do not override the loop's progress toward the set point.

### Where should I store feedback from previous loop iterations?

Persistent feedback should be stored in `.github/agent-memory/<task>.md` according to the [`references/memory-template.md`](https://github.com/humanlayer/skills/blob/main/references/memory-template.md) specification. This **memory** file is loaded by the actuator on every iteration and can contain reviewer comments, false-positive exclusions, or tuning parameters. By maintaining this state between runs, the loop adapts to repository-specific nuances and avoids repeating rejected proposals.