Skip to main content

Agent Profiles

An Agent Profile is a named, org-level role: "Backend Engineer," "Code Reviewer," "DevOps / Release." It's the single place that answers "what is this agent allowed to do, and with what?"

Like a Registry Resource, an Agent Profile is two things:

  1. IdentityprofileKey, name, description, status — set once when you create it.
  2. One or more published versions — the actual configuration, built from the form and its live preview — each Publish click adds a new one.

Anatomy of a profile

key: backend-developer
version: 1
name: Backend Developer
description: Java/Spring backend implementation.
role: Senior backend engineer
objective: Implement the requested change and leave verifiable evidence.
instructions: [Inspect the existing design before editing, Run the relevant tests]
guardrails: [Never report a test as passing unless it was executed]
required_outputs: { testResult: [PASS, FAIL] }
output_contract: { resultContract: out/result-contract.json }

runtime:
provider_key: claude-code # which Runtime Provider (claude-code, openai-codex, ...)
model: claude-sonnet-4-20250514
max_turns: 120
timeout_seconds: 2700
permission_mode: acceptEdits
memory_provider: cognee # optional
memory_connection_key: memory-main # must exist as a ProjectConnection on the dispatching project
adapter_config:
settings:
permissions:
allow: ["Read({workspace}/**)", "Write({workspace}/**)", "Bash(./mvnw *)"]
deny: ["Bash(rm -rf / *)"]
loop:
enabled: true
max_iterations: 3
contract_validation: true
stop_when: [resultContractValid, requiredArtifactsUploaded, gitPushedIfRequired]

workspace:
needs_repository: true

permission_presets: ["repository-write@1", "git-delivery@1"]

action_gates:
- match: "Bash(git push *)"
approver_role: TECH_LEAD
timeout_minutes: 240
on_timeout: deny

resources:
skills: [{ ref: "java-spring-boot@1", required: true }]
hooks: [{ ref: "java-checkstyle@1" }, { ref: "write-handoff@1" }]
plugins: [{ ref: "code-review-plugin@1", required: false }]
mcp_servers: [{ ref: "github-mcp@1" }]
prompts: [{ ref: "implementation-checklist@1", required: false }]
knowledge: [{ ref: "team-conventions@1", required: true }]

secrets:
- name: jira-token
ref: project/{projectKey}/connections/jira-main/token

cost_limit_usd: 15

Reasoning fields and enforced controls

role, objective, instructions, guardrails, constraints, and responsibilities are added to the agent's prompt. They improve task execution but are not security controls. Permissions, action gates, result-contract validation, required artifacts, and workspace policy are enforced by the runtime or platform.

The two permission layers — don't confuse them

  • runtime.adapter_config contains provider-native settings. For the Claude adapter, settings.permissions and merged permission_presets are written to Claude Code's settings.json and enforced tool call by tool call.
  • action_gates are platform-level intercepts, delivered via --permission-prompt-tool mcp__agentflow_gate__check. When a gated action fires, the run pauses and waits for a human with the right role to approve or deny it — this is how "git push requires Tech Lead sign-off" actually works.

A profile can use native permissions and action gates together, but not for the same action: use deny when an action must never run, and use an action_gate when it may run after approval. Never combine action_gates with runtime.permission_mode: bypassPermissions: the permission-prompt hook does not run in bypass mode, and publishing that combination is rejected.

Field naming is strict

The publish validator only accepts canonical snake_case field names (max_turns, not maxTurns; provider_key, not providerKey or adapterKey) and rejects a handful of legacy/stale shapes from earlier design docs (a bare top-level settings: block, workspace.repository_selector, artifact_policy.include_globs). If a publish fails with "... is not canonical," the fix is almost always renaming a camelCase key to snake_case or moving it out of a rejected block — see Agent Profile Schema for the exact list.

Permission Presets vs. inline permissions

permission_presets are versioned, reusable PERMISSION_PRESET registry resources (e.g. repository-write@1) merged into the effective policy at dispatch time — deny always wins across every merged preset and the profile's own inline allow/deny. Prefer presets for anything more than one profile needs (git delivery rules, Java build commands); keep inline permissions for what's genuinely specific to that one profile.