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. Identity — profileKey, 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.