diff --git a/README.md b/README.md
index 0377bd1..72236d0 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@ Every AI framework has its own structure. There's no universal, portable way to
- **Git-native** — Version control, branching, diffing, and collaboration built in
- **Framework-agnostic** — Export to any framework with adapters
-- **Compliance-ready** — First-class support for FINRA, Federal Reserve, and SEC regulatory requirements
+- **Compliance-ready** — First-class support for FINRA, Federal Reserve, SEC, and segregation of duties
- **Composable** — Agents can extend, depend on, and delegate to other agents
## The Standard
@@ -34,6 +34,7 @@ my-agent/
│
│ # ── Behavior & Rules ──────────────────────────────────
├── RULES.md # Hard constraints, must-always/must-never, safety boundaries
+├── DUTIES.md # Segregation of duties policy and role boundaries
├── AGENTS.md # Framework-agnostic fallback instructions
│
│ # ── Capabilities ──────────────────────────────────────
@@ -58,7 +59,8 @@ my-agent/
├── agents/ # Sub-agent definitions (recursive structure)
│ └── fact-checker/
│ ├── agent.yaml
-│ └── SOUL.md
+│ ├── SOUL.md
+│ └── DUTIES.md # This agent's role, permissions, boundaries
├── examples/ # Calibration interactions (few-shot)
│
│ # ── Runtime ───────────────────────────────────────────
@@ -76,6 +78,31 @@ When an agent learns a new skill or writes to memory, it opens a branch + PR for
+### Segregation of Duties (SOD)
+No single agent should control a critical process end-to-end. Define roles (`maker`, `checker`, `executor`, `auditor`), a conflict matrix (which roles can't be the same agent), and handoff workflows — all in `agent.yaml` + `DUTIES.md`. The validator catches violations before deployment.
+
+```yaml
+compliance:
+ segregation_of_duties:
+ roles:
+ - id: maker
+ description: Creates proposals
+ permissions: [create, submit]
+ - id: checker
+ description: Reviews and approves
+ permissions: [review, approve, reject]
+ conflicts:
+ - [maker, checker] # maker cannot approve own work
+ assignments:
+ loan-originator: [maker]
+ credit-reviewer: [checker]
+ handoffs:
+ - action: credit_decision
+ required_roles: [maker, checker]
+ approval_required: true
+ enforcement: strict
+```
+
### Live Agent Memory
The `memory/` folder holds a `runtime/` subfolder where agents write live knowledge — `dailylog.md`, `key-decisions.md`, and `context.md` — persisting state across sessions.
@@ -183,6 +210,18 @@ compliance:
model_risk:
validation_cadence: quarterly
ongoing_monitoring: true
+ segregation_of_duties:
+ roles:
+ - id: analyst
+ permissions: [create, submit]
+ - id: reviewer
+ permissions: [review, approve, reject]
+ conflicts:
+ - [analyst, reviewer]
+ assignments:
+ compliance-analyst: [analyst]
+ fact-checker: [reviewer]
+ enforcement: strict
```
## CLI Commands
@@ -218,6 +257,16 @@ gitagent has first-class support for financial regulatory compliance:
- **Reg S-P** — Customer privacy, PII handling
- **CFPB Circular 2022-03** — Explainable adverse action, Less Discriminatory Alternative search
+### Segregation of Duties
+- **Roles & Permissions** — Define maker, checker, executor, auditor roles with controlled permissions
+- **Conflict Matrix** — Declare which role pairs cannot be held by the same agent
+- **Handoff Workflows** — Require multi-agent participation for critical actions (credit decisions, regulatory filings)
+- **Isolation** — Full state and credential segregation between roles
+- **DUTIES.md** — Root-level policy + per-agent role declarations
+- **Enforcement** — Strict (blocks deployment) or advisory (warnings only)
+
+Inspired by [Salient AI](https://www.trysalient.com/)'s purpose-built agent architecture and the [FINOS AI Governance Framework](https://air-governance-framework.finos.org/mitigations/mi-22_multi-agent-isolation-and-segmentation.html).
+
Run `gitagent audit` for a full compliance checklist against your agent configuration.
## Adapters
@@ -264,7 +313,7 @@ See the `examples/` directory:
- **`examples/minimal/`** — 2-file hello world (agent.yaml + SOUL.md)
- **`examples/standard/`** — Code review agent with skills, tools, and rules
-- **`examples/full/`** — Production compliance agent with all directories, hooks, workflows, sub-agents, and regulatory artifacts
+- **`examples/full/`** — Production compliance agent with all directories, hooks, workflows, sub-agents, SOD with DUTIES.md, and regulatory artifacts
- **`examples/gitagent-helper/`** — Helper agent that assists with creating gitagent definitions
- **`examples/lyzr-agent/`** — Example Lyzr Studio integration
diff --git a/examples/full/DUTIES.md b/examples/full/DUTIES.md
new file mode 100644
index 0000000..b3ba390
--- /dev/null
+++ b/examples/full/DUTIES.md
@@ -0,0 +1,40 @@
+# Duties
+
+System-wide segregation of duties policy for the compliance-analyst agent system.
+
+## Roles
+
+| Role | Agent | Permissions | Description |
+|------|-------|-------------|-------------|
+| Analyst | compliance-analyst | create, submit | Performs regulatory analysis, generates findings and reports |
+| Reviewer | fact-checker | review, approve, reject | Reviews analysis for accuracy, verifies claims against authoritative sources |
+| Auditor | (unassigned) | audit, report | Audits completed reviews and maintains the compliance trail |
+
+## Conflict Matrix
+
+No single agent may hold both roles in any pair:
+
+- **Analyst <-> Reviewer** — The agent that produces findings cannot approve them
+- **Analyst <-> Auditor** — The agent that produces findings cannot audit them
+- **Reviewer <-> Auditor** — The agent that approves findings cannot audit the approval
+
+## Handoff Workflows
+
+### Regulatory Filing
+1. **Analyst** creates the filing draft and submits for review
+2. **Reviewer** verifies accuracy against authoritative sources, approves or rejects
+3. Approval required at each step before proceeding
+
+### Customer Communication
+1. **Analyst** drafts the communication
+2. **Reviewer** checks for FINRA 2210 compliance (fair, balanced, no misleading statements)
+3. Approval required before any communication is sent
+
+## Isolation Policy
+
+- **State isolation: full** — Each agent operates with its own memory and state. No agent may read or modify another agent's working memory.
+- **Credential segregation: separate** — Each role has its own credential scope. The analyst's data access credentials are distinct from the reviewer's.
+
+## Enforcement
+
+Enforcement mode is **strict**. Any SOD violation (e.g., assigning conflicting roles to the same agent) will fail validation and block deployment.
diff --git a/examples/full/agent.yaml b/examples/full/agent.yaml
index 986edff..5257175 100644
--- a/examples/full/agent.yaml
+++ b/examples/full/agent.yaml
@@ -96,6 +96,42 @@ compliance:
soc_report_required: true
vendor_ai_notification: true
subcontractor_assessment: true
+ segregation_of_duties:
+ roles:
+ - id: analyst
+ description: Performs regulatory analysis and generates findings
+ permissions:
+ - create
+ - submit
+ - id: reviewer
+ description: Reviews analysis for accuracy and completeness
+ permissions:
+ - review
+ - approve
+ - reject
+ - id: auditor
+ description: Audits completed reviews and maintains compliance trail
+ permissions:
+ - audit
+ - report
+ conflicts:
+ - [analyst, reviewer]
+ - [analyst, auditor]
+ - [reviewer, auditor]
+ assignments:
+ compliance-analyst: [analyst]
+ fact-checker: [reviewer]
+ isolation:
+ state: full
+ credentials: separate
+ handoffs:
+ - action: regulatory_filing
+ required_roles: [analyst, reviewer]
+ approval_required: true
+ - action: customer_communication
+ required_roles: [analyst, reviewer]
+ approval_required: true
+ enforcement: strict
tags:
- compliance
- financial-services
diff --git a/examples/full/agents/fact-checker/DUTIES.md b/examples/full/agents/fact-checker/DUTIES.md
new file mode 100644
index 0000000..5367a44
--- /dev/null
+++ b/examples/full/agents/fact-checker/DUTIES.md
@@ -0,0 +1,36 @@
+# Duties
+
+## Role
+
+**Reviewer** — Reviews analysis for accuracy and completeness.
+
+## Permissions
+
+- **review** — Examine outputs produced by the analyst
+- **approve** — Approve findings that meet accuracy and compliance standards
+- **reject** — Reject findings that are inaccurate, incomplete, or non-compliant
+
+## Boundaries
+
+### Must
+- Verify all factual claims against authoritative regulatory sources before approving
+- Reject any finding that cannot be independently verified
+- Document the basis for every approval or rejection decision
+
+### Must Not
+- Create original analysis or findings (analyst role only)
+- Modify the analyst's work — only approve or reject
+- Access the analyst's working state or memory
+- Use credentials assigned to other roles
+- Audit own review decisions (auditor role only)
+
+## Handoff Participation
+
+| Action | Position in Chain | Receives From | Hands Off To |
+|--------|------------------|---------------|--------------|
+| regulatory_filing | Step 2 | analyst | (terminal — approved or rejected) |
+| customer_communication | Step 2 | analyst | (terminal — approved or rejected) |
+
+## Isolation
+
+This agent operates under **full state isolation** with **separate credentials**. It cannot access the compliance-analyst's memory, state, or data access tokens.
diff --git a/examples/full/compliance/regulatory-map.yaml b/examples/full/compliance/regulatory-map.yaml
index 091c534..83ff16d 100644
--- a/examples/full/compliance/regulatory-map.yaml
+++ b/examples/full/compliance/regulatory-map.yaml
@@ -77,3 +77,26 @@ mappings:
controls:
- vendor_supervisory_procedures
- vendor_ai_change_notification
+
+ - capability: duty_segregation
+ rules:
+ - id: finos-ai-governance
+ name: "FINOS AI Governance — Multi-Agent Isolation"
+ controls:
+ - role_definition
+ - conflict_matrix_enforcement
+ - assignment_validation
+ - state_isolation
+ - credential_segregation
+ - id: finra-3110-sod
+ name: "FINRA Rule 3110 — Supervisory Separation"
+ controls:
+ - maker_checker_separation
+ - approval_workflow_enforcement
+ - independent_review_requirement
+ - id: soc2-logical-access
+ name: "SOC 2 — Logical Access Controls"
+ controls:
+ - role_based_access_control
+ - credential_isolation
+ - cross_boundary_approval
diff --git a/spec/SPECIFICATION.md b/spec/SPECIFICATION.md
index 44eeb59..a57ce3a 100644
--- a/spec/SPECIFICATION.md
+++ b/spec/SPECIFICATION.md
@@ -9,7 +9,7 @@
The standard is designed to be:
- **Framework-agnostic** — works with Claude Code, OpenAI, LangChain, CrewAI, AutoGen, and others
- **Git-native** — version control, branching, diffing, and collaboration built in
-- **Compliance-ready** — first-class support for FINRA, Federal Reserve, and interagency regulatory requirements
+- **Compliance-ready** — first-class support for FINRA, Federal Reserve, interagency regulatory requirements, and segregation of duties
- **Composable** — agents can extend, depend on, and delegate to other agents
## 2. Directory Structure
@@ -19,6 +19,7 @@ my-agent/
├── agent.yaml # [REQUIRED] Agent manifest
├── SOUL.md # [REQUIRED] Identity and personality
├── RULES.md # Hard constraints and boundaries
+├── DUTIES.md # Segregation of duties policy and role declaration
├── AGENTS.md # Framework-agnostic fallback instructions
├── README.md # Human documentation
├── skills/ # Reusable capability modules
@@ -194,6 +195,48 @@ compliance:
soc_report_required: false # SOC 2 report required
vendor_ai_notification: true # Vendor must notify of AI changes
subcontractor_assessment: false # Fourth-party risk assessed
+
+ # Segregation of duties (multi-agent duty separation)
+ segregation_of_duties:
+ roles: # Define roles for agents (min 2)
+ - id: maker # Initiates/creates
+ description: Creates proposals and initiates actions
+ permissions: [create, submit]
+ - id: checker # Reviews/approves
+ description: Reviews and approves maker outputs
+ permissions: [review, approve, reject]
+ - id: executor # Executes approved work
+ description: Executes approved actions
+ permissions: [execute]
+ - id: auditor # Audits completed work
+ description: Reviews completed actions for compliance
+ permissions: [audit, report]
+
+ conflicts: # SOD conflict matrix
+ - [maker, checker] # Maker cannot approve own work
+ - [maker, auditor] # Maker cannot audit own work
+ - [executor, checker] # Executor cannot approve what they execute
+ - [executor, auditor] # Executor cannot audit own execution
+
+ assignments: # Bind roles to agents
+ loan-originator: [maker]
+ credit-reviewer: [checker]
+ loan-processor: [executor]
+ compliance-auditor: [auditor]
+
+ isolation:
+ state: full # full | shared | none
+ credentials: separate # separate | shared
+
+ handoffs: # Critical actions requiring multi-role handoff
+ - action: credit_decision
+ required_roles: [maker, checker]
+ approval_required: true
+ - action: loan_disbursement
+ required_roles: [maker, checker, executor]
+ approval_required: true
+
+ enforcement: strict # strict | advisory
```
### Example Minimal agent.yaml
@@ -365,6 +408,30 @@ For regulated agents, RULES.md should include explicit regulatory constraints:
- Never transmit restricted data across jurisdictional boundaries
```
+## 5a. DUTIES.md — Segregation of Duties
+
+Declares the agent's duties, role boundaries, and the system-wide SOD policy. DUTIES.md exists at two levels:
+
+**Root level** (`DUTIES.md`) — Documents the system-wide segregation of duties policy: all roles, the conflict matrix, handoff workflows, isolation policy, and enforcement mode. This is the SOD equivalent of `RULES.md` — it defines the policy that all agents in the system must follow.
+
+**Per-agent level** (`agents//DUTIES.md`) — Declares this specific agent's role, permissions, boundaries, and handoff participation. Each sub-agent's DUTIES.md answers: what is my role, what can I do, what must I not do, and who do I hand off to.
+
+### Root DUTIES.md Recommended Sections
+
+- **Roles** — Table of all roles, assigned agents, and permissions
+- **Conflict Matrix** — Which role pairs cannot be held by the same agent
+- **Handoff Workflows** — Step-by-step handoff chains for critical actions
+- **Isolation Policy** — State and credential isolation levels
+- **Enforcement** — Strict vs advisory mode
+
+### Per-Agent DUTIES.md Recommended Sections
+
+- **Role** — This agent's assigned role
+- **Permissions** — What actions this agent can take
+- **Boundaries** — Must/must-not rules specific to this role
+- **Handoff Participation** — Where this agent sits in handoff chains
+- **Isolation** — This agent's isolation constraints
+
## 6. AGENTS.md — Framework-Agnostic Instructions
Provides fallback instructions compatible with Cursor, Copilot, and other tools that read `AGENTS.md`. This file supplements `agent.yaml` + `SOUL.md` for systems that don't understand the gitagent format.
@@ -864,6 +931,13 @@ A valid gitagent repository must:
5. All referenced tools must exist in `tools/`
6. All referenced sub-agents must exist in `agents/`
7. `hooks.yaml` scripts must exist at specified paths
+8. If `compliance.segregation_of_duties` is present:
+ - `roles` must define at least 2 roles with unique IDs
+ - `conflicts` pairs must reference defined role IDs
+ - `assignments` must reference defined role IDs
+ - No agent in `assignments` may hold roles that appear together in `conflicts`
+ - `handoffs.required_roles` must reference defined role IDs and include at least 2
+ - Assigned agents should exist in the `agents` section
## 19. CLI Commands
@@ -942,6 +1016,15 @@ All schemas are in `spec/schemas/`:
| SR 21-8 | BSA/AML Model Risk | `compliance.model_risk` for AML agents |
| CFPB Circular 2022-03 | Adverse Action + AI | `compliance.data_governance.lda_search` |
+### Segregation of Duties References
+
+| Document | Subject | gitagent Impact |
+|----------|---------|-----------------|
+| FINOS AI Governance Framework | Multi-Agent Isolation & Segmentation | `compliance.segregation_of_duties` |
+| SOC 2 Type II | Logical Access Controls | `segregation_of_duties.isolation` |
+| SR 11-7 Section IV | Independent Review | `segregation_of_duties.conflicts` (maker/checker separation) |
+| FINRA 3110 | Supervisory Systems (duty separation) | `segregation_of_duties.handoffs` |
+
---
*This specification is a living document. Contributions welcome.*
diff --git a/spec/schemas/agent-yaml.schema.json b/spec/schemas/agent-yaml.schema.json
index 6bf666c..acb7d91 100644
--- a/spec/schemas/agent-yaml.schema.json
+++ b/spec/schemas/agent-yaml.schema.json
@@ -429,6 +429,9 @@
},
"vendor_management": {
"$ref": "#/$defs/vendor_management_config"
+ },
+ "segregation_of_duties": {
+ "$ref": "#/$defs/segregation_of_duties_config"
}
},
"additionalProperties": false,
@@ -662,6 +665,116 @@
}
},
"additionalProperties": false
+ },
+
+ "segregation_of_duties_config": {
+ "type": "object",
+ "description": "Segregation of duties configuration for multi-agent systems. Ensures no single agent has complete control over critical processes.",
+ "properties": {
+ "roles": {
+ "type": "array",
+ "description": "Roles that agents can hold in this system",
+ "items": {
+ "type": "object",
+ "required": ["id", "description"],
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^[a-z][a-z0-9_]*$",
+ "description": "Role identifier (snake_case)"
+ },
+ "description": {
+ "type": "string",
+ "description": "Human-readable role description"
+ },
+ "permissions": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": ["create", "submit", "review", "approve", "reject", "execute", "audit", "report"]
+ },
+ "uniqueItems": true,
+ "description": "Permissions granted to this role"
+ }
+ },
+ "additionalProperties": false
+ },
+ "minItems": 2
+ },
+ "conflicts": {
+ "type": "array",
+ "description": "Pairs of role IDs that cannot be held by the same agent (SOD matrix)",
+ "items": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "minItems": 2,
+ "maxItems": 2
+ }
+ },
+ "assignments": {
+ "type": "object",
+ "description": "Maps agent names to their assigned roles",
+ "additionalProperties": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "minItems": 1
+ }
+ },
+ "isolation": {
+ "type": "object",
+ "description": "Isolation level between agents with different roles",
+ "properties": {
+ "state": {
+ "type": "string",
+ "enum": ["full", "shared", "none"],
+ "description": "'full': agents cannot access each other's state. 'shared': read-only cross-access. 'none': no isolation."
+ },
+ "credentials": {
+ "type": "string",
+ "enum": ["separate", "shared"],
+ "description": "'separate': each role has its own credential scope. 'shared': agents share credentials."
+ }
+ },
+ "additionalProperties": false
+ },
+ "handoffs": {
+ "type": "array",
+ "description": "Critical actions requiring multi-role participation",
+ "items": {
+ "type": "object",
+ "required": ["action", "required_roles"],
+ "properties": {
+ "action": {
+ "type": "string",
+ "description": "Action type requiring handoff (e.g., credit_decision, loan_disbursement)"
+ },
+ "required_roles": {
+ "type": "array",
+ "items": { "type": "string" },
+ "minItems": 2,
+ "description": "Roles that must participate sequentially"
+ },
+ "approval_required": {
+ "type": "boolean",
+ "description": "Whether explicit approval is needed at each handoff",
+ "default": true
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "enforcement": {
+ "type": "string",
+ "enum": ["strict", "advisory"],
+ "description": "'strict': SOD violations are errors. 'advisory': SOD violations are warnings.",
+ "default": "strict"
+ }
+ },
+ "additionalProperties": false
}
}
}
diff --git a/src/adapters/claude-code.ts b/src/adapters/claude-code.ts
index 104cf59..b9c074a 100644
--- a/src/adapters/claude-code.ts
+++ b/src/adapters/claude-code.ts
@@ -26,6 +26,12 @@ export function exportToClaudeCode(dir: string): string {
parts.push(rules);
}
+ // DUTIES.md → segregation of duties policy
+ const duty = loadFileIfExists(join(agentDir, 'DUTIES.md'));
+ if (duty) {
+ parts.push(duty);
+ }
+
// Skills — loaded via skill-loader
const skillsDir = join(agentDir, 'skills');
const skills = loadAllSkills(skillsDir);
@@ -77,6 +83,36 @@ export function exportToClaudeCode(dir: string): string {
complianceParts.push('- All actions are audit-logged');
}
+ if (c.segregation_of_duties) {
+ const sod = c.segregation_of_duties;
+ complianceParts.push('\n### Segregation of Duties');
+ complianceParts.push(`Enforcement: ${sod.enforcement ?? 'strict'}`);
+ if (sod.assignments) {
+ complianceParts.push('\nRole assignments:');
+ for (const [agent, roles] of Object.entries(sod.assignments)) {
+ complianceParts.push(`- ${agent}: ${roles.join(', ')}`);
+ }
+ }
+ if (sod.conflicts) {
+ complianceParts.push('\nConflict rules (must not be same agent):');
+ for (const [a, b] of sod.conflicts) {
+ complianceParts.push(`- ${a} <-> ${b}`);
+ }
+ }
+ if (sod.handoffs) {
+ complianceParts.push('\nRequired handoffs:');
+ for (const h of sod.handoffs) {
+ complianceParts.push(`- ${h.action}: ${h.required_roles.join(' → ')}`);
+ }
+ }
+ if (sod.isolation?.state === 'full') {
+ complianceParts.push('- Agent state is fully isolated per role');
+ }
+ if (sod.isolation?.credentials === 'separate') {
+ complianceParts.push('- Credentials are segregated per role');
+ }
+ }
+
parts.push(complianceParts.join('\n'));
}
diff --git a/src/adapters/system-prompt.ts b/src/adapters/system-prompt.ts
index 782bc12..95f5ede 100644
--- a/src/adapters/system-prompt.ts
+++ b/src/adapters/system-prompt.ts
@@ -25,6 +25,12 @@ export function exportToSystemPrompt(dir: string): string {
parts.push(rules);
}
+ // DUTIES.md
+ const duty = loadFileIfExists(join(agentDir, 'DUTIES.md'));
+ if (duty) {
+ parts.push(duty);
+ }
+
// Skills — loaded via skill-loader
const skillsDir = join(agentDir, 'skills');
const skills = loadAllSkills(skillsDir);
@@ -82,6 +88,37 @@ export function exportToSystemPrompt(dir: string): string {
constraints.push('- Do not process any personally identifiable information');
}
+ if (c.segregation_of_duties) {
+ const sod = c.segregation_of_duties;
+ constraints.push('- Segregation of duties is enforced:');
+ if (sod.assignments) {
+ for (const [agentName, roles] of Object.entries(sod.assignments)) {
+ constraints.push(` - Agent "${agentName}" has role(s): ${roles.join(', ')}`);
+ }
+ }
+ if (sod.conflicts) {
+ constraints.push('- Duty separation rules (no single agent may hold both):');
+ for (const [a, b] of sod.conflicts) {
+ constraints.push(` - ${a} and ${b}`);
+ }
+ }
+ if (sod.handoffs) {
+ constraints.push('- The following actions require multi-agent handoff:');
+ for (const h of sod.handoffs) {
+ constraints.push(` - ${h.action}: must pass through roles ${h.required_roles.join(' → ')}${h.approval_required !== false ? ' (approval required)' : ''}`);
+ }
+ }
+ if (sod.isolation?.state === 'full') {
+ constraints.push('- Agent state/memory is fully isolated per role — do not access another agent\'s state');
+ }
+ if (sod.isolation?.credentials === 'separate') {
+ constraints.push('- Credentials are segregated per role — use only credentials assigned to your role');
+ }
+ if (sod.enforcement === 'strict') {
+ constraints.push('- SOD enforcement is STRICT — violations will block execution');
+ }
+ }
+
if (constraints.length > 0) {
parts.push(`## Compliance Constraints\n${constraints.join('\n')}`);
}
diff --git a/src/commands/audit.ts b/src/commands/audit.ts
index cfeda64..1d4a5ed 100644
--- a/src/commands/audit.ts
+++ b/src/commands/audit.ts
@@ -162,8 +162,65 @@ export const auditCommand = new Command('audit')
info(' No vendor dependencies — vendor management not required');
}
+ // Segregation of Duties
+ heading('8. Segregation of Duties');
+ if (c.segregation_of_duties) {
+ const sod = c.segregation_of_duties;
+ auditCheck('Roles defined (≥2)', !!(sod.roles && sod.roles.length >= 2));
+ auditCheck('Conflict matrix defined', !!(sod.conflicts && sod.conflicts.length > 0));
+ auditCheck('Role assignments configured', !!(sod.assignments && Object.keys(sod.assignments).length > 0));
+ auditCheck('State isolation configured', !!sod.isolation?.state);
+ auditCheck('State isolation is full', sod.isolation?.state === 'full');
+ auditCheck('Credential segregation configured', !!sod.isolation?.credentials);
+ auditCheck('Credentials are separate', sod.isolation?.credentials === 'separate');
+ auditCheck('Handoff workflows defined', !!(sod.handoffs && sod.handoffs.length > 0));
+ auditCheck('Enforcement is strict', sod.enforcement === 'strict');
+
+ if (sod.assignments) {
+ info(' Role assignments:');
+ for (const [agent, roles] of Object.entries(sod.assignments)) {
+ label(' ' + agent, roles.join(', '));
+ }
+ }
+
+ if (sod.conflicts) {
+ info(' Conflict rules:');
+ for (const [a, b] of sod.conflicts) {
+ info(` ${a} <-> ${b}`);
+ }
+ }
+
+ // Check for SOD violations in assignments
+ if (sod.assignments && sod.conflicts) {
+ let violationFound = false;
+ for (const [agentName, assignedRoles] of Object.entries(sod.assignments)) {
+ for (const [roleA, roleB] of sod.conflicts) {
+ if (assignedRoles.includes(roleA) && assignedRoles.includes(roleB)) {
+ error(` VIOLATION: Agent "${agentName}" holds conflicting roles "${roleA}" and "${roleB}"`);
+ violationFound = true;
+ }
+ }
+ }
+ if (!violationFound) {
+ success(' No SOD violations detected in role assignments');
+ }
+ }
+
+ if (sod.handoffs) {
+ info(' Handoff requirements:');
+ for (const h of sod.handoffs) {
+ label(' ' + h.action, `${h.required_roles.join(' → ')} (approval: ${h.approval_required !== false ? 'yes' : 'no'})`);
+ }
+ }
+ } else if (manifest.agents && Object.keys(manifest.agents).length >= 2) {
+ warn(' Segregation of duties not configured for multi-agent system');
+ warn(' Consider adding segregation_of_duties for duty separation controls');
+ } else {
+ info(' Single-agent system — segregation of duties not applicable');
+ }
+
// Compliance artifacts
- heading('8. Compliance Artifacts');
+ heading('9. Compliance Artifacts');
auditCheck('compliance/ directory exists', existsSync(join(dir, 'compliance')));
auditCheck('regulatory-map.yaml exists', existsSync(join(dir, 'compliance', 'regulatory-map.yaml')));
auditCheck('validation-schedule.yaml exists', existsSync(join(dir, 'compliance', 'validation-schedule.yaml')));
@@ -171,7 +228,7 @@ export const auditCommand = new Command('audit')
auditCheck('RULES.md exists', existsSync(join(dir, 'RULES.md')));
// Hooks for audit trail
- heading('9. Audit Hooks');
+ heading('10. Audit Hooks');
const hooksExist = existsSync(join(dir, 'hooks', 'hooks.yaml'));
auditCheck('hooks/hooks.yaml exists', hooksExist);
if (hooksExist) {
diff --git a/src/commands/init.ts b/src/commands/init.ts
index 3eff35f..38c9f55 100644
--- a/src/commands/init.ts
+++ b/src/commands/init.ts
@@ -262,6 +262,36 @@ metadata:
Describe the skill instructions here.
`;
+const FULL_DUTIES_MD = `# Duties
+
+System-wide segregation of duties policy.
+
+## Roles
+
+| Role | Agent | Permissions | Description |
+|------|-------|-------------|-------------|
+| (define roles) | (assign agents) | (list permissions) | (describe duty) |
+
+## Conflict Matrix
+
+No single agent may hold both roles in any pair:
+
+- (define role conflicts)
+
+## Handoff Workflows
+
+(Define critical actions that require multi-role handoff)
+
+## Isolation Policy
+
+- **State isolation:** (full | shared | none)
+- **Credential segregation:** (separate | shared)
+
+## Enforcement
+
+(strict | advisory)
+`;
+
const REGULATORY_MAP = `mappings: []
`;
@@ -346,6 +376,7 @@ export const initCommand = new Command('init')
createFile(join(dir, 'SOUL.md'), STANDARD_SOUL_MD);
createFile(join(dir, 'RULES.md'), FULL_RULES_MD);
createFile(join(dir, 'AGENTS.md'), AGENTS_MD);
+ createFile(join(dir, 'DUTIES.md'), FULL_DUTIES_MD);
createDir(join(dir, 'skills', 'example-skill'));
createFile(join(dir, 'skills', 'example-skill', 'SKILL.md'), SKILL_MD);
@@ -392,6 +423,7 @@ export const initCommand = new Command('init')
success('Created SOUL.md');
success('Created RULES.md');
success('Created AGENTS.md');
+ success('Created DUTIES.md');
success('Created skills/example-skill/SKILL.md');
success('Created knowledge/index.yaml');
success('Created memory/MEMORY.md + memory.yaml');
diff --git a/src/commands/validate.ts b/src/commands/validate.ts
index a5fa696..b568a21 100644
--- a/src/commands/validate.ts
+++ b/src/commands/validate.ts
@@ -236,6 +236,140 @@ function validateCompliance(dir: string): ValidationResult {
}
}
+ // Segregation of Duties validation
+ const sod = c.segregation_of_duties;
+ if (sod) {
+ const roleIds = sod.roles?.map(r => r.id) ?? [];
+
+ // Must define at least 2 roles
+ if (!sod.roles || sod.roles.length < 2) {
+ result.valid = false;
+ result.errors.push('[SOD] segregation_of_duties.roles must define at least 2 roles');
+ }
+
+ // Role IDs must be unique
+ if (roleIds.length !== new Set(roleIds).size) {
+ result.valid = false;
+ result.errors.push('[SOD] segregation_of_duties.roles contains duplicate role IDs');
+ }
+
+ // Conflict pairs must reference defined roles
+ if (sod.conflicts) {
+ for (const pair of sod.conflicts) {
+ for (const roleId of pair) {
+ if (!roleIds.includes(roleId)) {
+ result.valid = false;
+ result.errors.push(
+ `[SOD] Conflict references undefined role "${roleId}". Defined roles: ${roleIds.join(', ')}`
+ );
+ }
+ }
+ if (pair[0] === pair[1]) {
+ result.valid = false;
+ result.errors.push(`[SOD] Role "${pair[0]}" cannot conflict with itself`);
+ }
+ }
+ }
+
+ // Assignments must reference defined roles and check for conflicts
+ if (sod.assignments) {
+ for (const [agentName, assignedRoles] of Object.entries(sod.assignments)) {
+ for (const roleId of assignedRoles) {
+ if (!roleIds.includes(roleId)) {
+ result.valid = false;
+ result.errors.push(`[SOD] Agent "${agentName}" assigned undefined role "${roleId}"`);
+ }
+ }
+
+ // Core SOD check: no agent holds conflicting roles
+ if (sod.conflicts) {
+ for (const [roleA, roleB] of sod.conflicts) {
+ if (assignedRoles.includes(roleA) && assignedRoles.includes(roleB)) {
+ const msg = `[SOD] Agent "${agentName}" holds conflicting roles: "${roleA}" and "${roleB}"`;
+ if (sod.enforcement === 'advisory') {
+ result.warnings.push(msg);
+ } else {
+ result.valid = false;
+ result.errors.push(msg);
+ }
+ }
+ }
+ }
+
+ // Assigned agents should exist in manifest.agents
+ if (manifest.agents && !manifest.agents[agentName]) {
+ result.warnings.push(`[SOD] Agent "${agentName}" in assignments not found in agents section`);
+ }
+ }
+ }
+
+ // Handoff required_roles must reference defined roles
+ if (sod.handoffs) {
+ for (const handoff of sod.handoffs) {
+ for (const roleId of handoff.required_roles) {
+ if (!roleIds.includes(roleId)) {
+ result.valid = false;
+ result.errors.push(
+ `[SOD] Handoff for "${handoff.action}" references undefined role "${roleId}"`
+ );
+ }
+ }
+ const uniqueRoles = new Set(handoff.required_roles);
+ if (uniqueRoles.size < 2) {
+ result.valid = false;
+ result.errors.push(
+ `[SOD] Handoff for "${handoff.action}" must require at least 2 distinct roles`
+ );
+ }
+ }
+ }
+
+ // High/critical risk tier recommendations
+ if (c.risk_tier === 'high' || c.risk_tier === 'critical') {
+ if (sod.enforcement === 'advisory') {
+ result.warnings.push(
+ `[SOD] Risk tier "${c.risk_tier}" recommends enforcement: "strict", got "advisory"`
+ );
+ }
+ if (!sod.isolation || sod.isolation.state !== 'full') {
+ result.warnings.push(
+ `[SOD] Risk tier "${c.risk_tier}" recommends isolation.state: "full" for full state segregation`
+ );
+ }
+ if (!sod.isolation || sod.isolation.credentials !== 'separate') {
+ result.warnings.push(
+ `[SOD] Risk tier "${c.risk_tier}" recommends isolation.credentials: "separate"`
+ );
+ }
+ }
+
+ // SOD without conflicts is meaningless
+ if (!sod.conflicts || sod.conflicts.length === 0) {
+ result.warnings.push(
+ '[SOD] No conflicts defined — segregation_of_duties without conflict rules has no enforcement value'
+ );
+ }
+
+ // Every role should be assigned to at least one agent
+ if (sod.assignments && sod.roles) {
+ const assignedRoleIds = new Set(Object.values(sod.assignments).flat());
+ for (const role of sod.roles) {
+ if (!assignedRoleIds.has(role.id)) {
+ result.warnings.push(`[SOD] Role "${role.id}" is defined but not assigned to any agent`);
+ }
+ }
+ }
+ }
+
+ // Recommend SOD for multi-agent high/critical risk setups
+ if (!sod && manifest.agents && Object.keys(manifest.agents).length >= 2) {
+ if (c.risk_tier === 'high' || c.risk_tier === 'critical') {
+ result.warnings.push(
+ '[SOD] Multi-agent system with high/critical risk tier — consider configuring segregation_of_duties'
+ );
+ }
+ }
+
return result;
}
diff --git a/src/utils/loader.ts b/src/utils/loader.ts
index 991ebcf..38f5084 100644
--- a/src/utils/loader.ts
+++ b/src/utils/loader.ts
@@ -115,6 +115,25 @@ export interface ComplianceConfig {
vendor_ai_notification?: boolean;
subcontractor_assessment?: boolean;
};
+ segregation_of_duties?: {
+ roles?: Array<{
+ id: string;
+ description: string;
+ permissions?: string[];
+ }>;
+ conflicts?: Array<[string, string]>;
+ assignments?: Record;
+ isolation?: {
+ state?: string;
+ credentials?: string;
+ };
+ handoffs?: Array<{
+ action: string;
+ required_roles: string[];
+ approval_required?: boolean;
+ }>;
+ enforcement?: string;
+ };
}
export function loadAgentManifest(dir: string): AgentManifest {