DEV Community

Doug Sillars for Cognous

Posted on

The Manifest That Keeps Your AI Agent Honest

In our first post, Cognous Keeps Your AI in Check, we introduced the scenario: a customer-service agent that pulls CRM records, drafts replies, and — if nobody's watching — occasionally sends 1,000 tone-deaf emails to your best clients. We said the fix isn't reading what the agent wrote after the fact. It's deciding, in advance, what the agent is even allowed to attempt.

That decision has to live somewhere. It can't live in a Slack thread, a comment in the agent's system prompt, or "Dave from platform remembers we blocked that." It needs to be a file — versioned, reviewable, and boring enough that a security team can actually sign off on it.

That's what Cognous's Open Control Stack is for. It's four small, open-source projects — Declare, Control, Replay, Evidence — that sit beside your agents and cover authorize, enforce, and prove. Declare comes first, and its artifact is the Agent Action Manifest. This post describes the Action Manifest tooling in the Open Control Stack, available on GitHub.

The Four Actions, Written Down

If you remember in post one, our AI agent was pulling customer data, drafted a cringey email, and sent it to our top 1,000 customers. The Action Manifest is used to outline the agent's permissions as declarations, to prevent this from ever happening again:

{
  "manifest_version": "0.1",
  "manifest_id": "customer-service-agent-manifest",
  "agent_name": "customer-service-agent",
  "owner": "support-platform-team",
  "environment": "production",
  "default_action": "block",
  "tools": [
    {
      "tool_name": "crm",
      "description": "Customer relationship management system",
      "allowed": true,
      "external_system": "crm.internal",
      "data_classification": "customer_pii"
    },
    {
      "tool_name": "email",
      "description": "Outbound customer email system",
      "allowed": true,
      "external_system": "smtp.internal",
      "data_classification": "customer_pii"
    }
  ],
  "actions": [
    {
      "action_name": "pull_top_customers",
      "tool_name": "crm",
      "action_type": "read",
      "description": "Retrieve top customer records from the CRM.",
      "default_action": "allow",
      "reliance_requirement": {
        "required": true,
        "allowed_source_types": ["tool", "database"]
      }
    },
    {
      "action_name": "draft_reply",
      "tool_name": "email",
      "action_type": "write",
      "description": "Draft a reply email for a customer inquiry.",
      "default_action": "allow",
      "authority_required": [
        {
          "scope": "email.draft.customer",
          "description": "Permission to draft outbound customer emails",
          "required": true,
          "source": "support-platform-team"
        }
      ],
      "review_requirement": {
        "mode": "draft_first"
      }
    },
    {
      "action_name": "pull_contract_details",
      "tool_name": "crm",
      "action_type": "read",
      "description": "Retrieve contract terms from the CRM.",
      "default_action": "block",
      "tags": ["contract", "restricted"]
    },
    {
      "action_name": "send_email",
      "tool_name": "email",
      "action_type": "external_send",
      "description": "Send an approved email to the customer.",
      "default_action": "escalate",
      "authority_required": [
        {
          "scope": "email.send.customer",
          "description": "Outbound customer send scope",
          "required": true,
          "source": "email.send.customer"
        }
      ],
      "review_requirement": {
        "mode": "approval_required",
        "reviewer_role": "support-lead"
      },
      "reliance_requirement": {
        "required": true,
        "allowed_source_types": ["tool", "user_input"]
      },
      "payload_policy": {
        "sensitive_fields": ["customer_email", "customer_name"],
        "forbidden_fields": ["contract_terms"]
      },
      "redaction_hints": [
        {
          "field_path": "customer_email",
          "reason": "PII in exported records"
        }
      ]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

(Field names here follow the public schema — check the repo for the exact current shape if you're implementing against it.)

Rules are only as good as what is defined. At the very top of the manifest, we explicitly cover anything not mentioned: "default_action": "block". So when a new tool is onboarded (for example send_tweet), you don't need to go back and update every manifest — it's blocked until you expressly give the agent permission to use it.

For those actions that are defined:

  • pull_top_customers and draft_reply are both allow, but not the same kind of allow: pulling the top customers is allowed, full stop, but the draft reply has review_requirement.mode: draft_first — the agent can write it, but it stops there.
  • send_email isn't just "escalate" as a vague gesture — it names the exact authority scope required (email.send.customer), the exact reviewer role (support-lead), and which payload fields have to be redacted before this record goes anywhere near an audit export.
  • pull_contract_details is the odd one out — it doesn't strictly need to be there. default_action: block already covers anything undeclared, so an unlisted "read contract details" action would be blocked anyway. It's included for visibility: a blocked action that shows up in the manifest, tagged restricted, is a documented decision someone can point to. A blocked action that's simply never mentioned looks identical to an oversight.

It's important to note that the manifest is not enforcement. It doesn't stop the agent from doing anything — a runtime layer has to actually read it and act on it (that'll be described in the next post). What the manifest gives you is a single artifact that says, in one place, what should happen — before the agent has run even once.

The Nine Action Types

In our simple manifest above, we show three possible action types. The manifest supports nine. These classifications are what give a governance review the ability to distinguish a harmless read from something that should never happen without a human:

Action Meaning
read retrieves or inspects information
write creates or modifies information, not necessarily sent externally
external_send sends information outside the system boundary
delete removes information or records
export packages or transfers information downstream
purchase initiates or prepares a purchase
approve approves or authorizes a workflow step
escalate routes to a human or supervisory process
other anything not otherwise classified

A tool list alone can't tell you this. Knowing the agent "has access to email" tells you nothing about whether it can draft, send, or both, and what's supposed to happen before each.

The Four Review Modes

Action type says what kind of thing an action is. Review mode says who has to look at it, and when — and it's doing just as much governance work in our example as action type is. There are four:

Mode Meaning
none no specific review posture declared
human_review a human is expected to look at it before execution or completion — no specific person named
approval_required approval is required from a named reviewer role
draft_first the agent produces a draft; it does not finish the action itself

The difference between human_review and approval_required is easy to miss but it matters: human_review says "someone should look at this" without saying who. approval_required is stricter — it names a reviewer role, and the validator enforces that: an approval_required action with no reviewer_role fails validation outright.

That's why send_email in our manifest uses approval_required with reviewer_role: "support-lead", not human_review. Sending customer email isn't "someone should probably glance at this" — it's "this specific role signs off, or it doesn't go out."

Worth being precise about what "support-lead" actually is here: a role string, nothing more. The manifest doesn't know who holds that role, doesn't know they're on Slack, and doesn't page anyone. Your enterprise-side plumbing converts "support lead" to Janice, and builds an alerting system in Slack to let her know that her approval is required. The public stack's job ends at "this role must approve, and there's a record of whether it did."

Reliance: A Third Thing Being Tracked

Authority asks whether an action was allowed. Review asks whether a person signed off. Reliance asks something different: what did this action actually depend on to produce its result?

A reliance_requirement declares that an action should leave a record of its source — a tool, a database, a file, an API, or user input. pull_top_customers declares reliance on tool and database, because a governance review should be able to see it actually came from the CRM and not somewhere the agent invented. send_email declares reliance on tool and user_input, tying the send back to the approved draft it was built from.

draft_reply and pull_contract_details don't declare a reliance requirement — which is why the summarize output above shows Actions requiring reliance: 2, not 4.

Validation Isn't Optional, and It Isn't Decoration

A manifest can be valid JSON, but be semantic nonsense. A delete action on the production database marked allow isn't caught by JSON Schema. It's caught by the validator, which checks things like:

  • does every action reference a tool that's actually declared and allowed?
  • is a privileged action type (external_send, write, delete, purchase, approve) missing an authority requirement it should have?
  • is an action marked approval_required but missing a reviewer role?
  • are there duplicate action or tool names?
  • are sensitive fields declared with no redaction hints to match?

Run it from the CLI — aam, short for Agent Action Manifest, is the command-line tool that ships with the manifest repo. First we run the validate command to ensure that the JSON is valid:

$ aam validate customer-service-agent-manifest.json
Validation result: VALID
Manifest ID:       customer-service-agent-manifest

Warnings (3):
  [W004] [actions[1](draft_reply)] Action 'draft_reply' has effective default_action 'allow' for action_type 'write'.
  [W006] [actions[1](draft_reply).payload_policy] Action 'draft_reply' has action_type 'write' but no payload_policy is declared.
  [W005] [actions[2](pull_contract_details).reliance_requirement] Action 'pull_contract_details' has action_type 'read' but no reliance_requirement is declared.
Enter fullscreen mode Exit fullscreen mode

VALID, but not silent — the validator still flags things worth a second look, even in a manifest that passes. Fair warnings, too: we didn't bother declaring a payload policy for a draft-only action, and a blocked action doesn't need reliance evidence it'll never produce.

Next we can summarize the manifest with the summarize command:

$ aam summarize customer-service-agent-manifest.json
Manifest ID:          customer-service-agent-manifest
Agent name:           customer-service-agent
Environment:          production
Tools:                2
Actions:              4

Actions by type:
  external_send: 1
  read: 2
  write: 1

Actions by default posture:
  allow: 2
  block: 1
  escalate: 1

Actions requiring authority: 2
Actions requiring review:    2
Actions requiring reliance:  2
Enter fullscreen mode Exit fullscreen mode

That "2" next to authority is easy to misread as just the send — it's actually draft_reply and send_email both. Drafting on a customer's behalf declares its own authority requirement (email.draft.customer), separate from the one on the final send (email.send.customer). Declaring it isn't the same as enforcing it — nothing here checks whether that authority actually exists. That check is the Control Plane's job, next post.

aam verifies that your manifest is ready, and gives the team a concise summary of what's allowed.

What This Deliberately Doesn't Do

The manifest is the Declare layer, and it stays in its lane:

  • it doesn't execute the agent
  • it doesn't enforce anything at runtime
  • it doesn't grant authority — email.send.customer being declared as required doesn't mean it exists
  • it doesn't replace your application's own authorization system

Declaring that sending email should require approval is not the same thing as stopping an unapproved send. That gap — between what's declared and what actually happens when the agent tries to act — is exactly what the next layer closes.

Next up

The manifest says what the agent may propose. It says nothing about what the agent actually does on a Tuesday afternoon when it's mid-run and reaching for the send button. That's the Agent Control Plane — it reads this exact manifest, sits beside the agent at runtime, and turns every proposed action into a recorded decision: allow, block, or escalate, deterministically, every time. That's next.

The manifest repo, examples, schemas, and test suite are live now: github.com/cogno-us/cognous-open-control-stack. Clone it, validate the customer-service example, break it on purpose and watch the validator catch you.

Top comments (2)

Collapse
 
reidmarlow profile image
Reid Marlow

The bit I like here is making action classes reviewable before the agent exists. I would add one more field for where evidence has to be captured, because the model should not be the only thing claiming it stayed inside the manifest.

Collapse
 
peterbuildssecure profile image
Peter

The enforcement layer should bind an approval to an immutable action instance, not just an action name and reviewer role. For send_email, I’d bind the approval to the tool, normalized payload or payload digest, recipient, manifest version, expiry, and a one-time nonce.

The runtime should reject it if the payload changes after review, the approval is replayed, the reviewer’s role has been revoked, or the manifest version no longer matches. Otherwise a valid approval record can end up authorizing a different email from the one the support lead actually reviewed.

I’d also keep the reviewer identity source outside anything the agent can write or summarize.