Skip to content

FR-004: Formal Ownership Transfer

Status: Proposed
Priority: Medium
Author: Tim McCrimmon
Created: 2026-06-10
Target Version: v0.4
Regulatory hook: EU AI Act Art. 14(4) (accountable person must have competence and authority)
Depends on: FR-002 (owner_id field)


Summary

Agent ownership is currently static — created_by is set at registration and never changes. When the accountable person for an agent changes (role change, departure, restructuring), there is no mechanism to transfer ownership and no record of the handoff. This FR adds a formal transfer workflow: the outgoing owner initiates, the incoming owner explicitly accepts, and the transfer is logged. Agents cannot become ownerless.


Problem Statement

The current state:

Agent registered → created_by = "alice" → alice leaves company → agent still running → owned by nobody

For EU AI Act Art. 14(4) compliance, the accountable person must have actual competence and authority. A departed employee cannot exercise oversight. The absence of a transfer mechanism means the accountability chain breaks silently and is invisible in the audit log.

Beyond compliance, this is an operational risk: high-stakes agents (healthcare, financial) running without an active owner is a real-world safety concern.


Proposed Solution

1. Transfer request

POST /api/agents/{id}/ownership/transfer
{
  "to_owner_id": "bob",
  "reason": "alice leaving team",
  "effective_at": "2026-07-01T00:00:00Z"  // optional; immediate if omitted
}

Creates a pending transfer record. Notifies the incoming owner (via FR-002's notification mechanism). Agent continues operating under current owner until accepted.

2. Acceptance

POST /api/agents/{id}/ownership/transfer/{transfer_id}/accept

Incoming owner explicitly accepts. At this point: - owner_id on the agent record is updated - Transfer record marked accepted - Audit log entry written: previous owner, new owner, timestamp, reason

3. Rejection / cancellation

POST /api/agents/{id}/ownership/transfer/{transfer_id}/reject
POST /api/agents/{id}/ownership/transfer/{transfer_id}/cancel

On reject, the transfer is void and the current owner retains ownership. On cancel (by initiator), same outcome. Both written to audit log.

4. Expiry and escalation

A pending transfer that is not accepted within a configurable window (default: 72 hours) escalates to an admin. Admins can force-assign ownership to prevent the ownerless state.

5. Ownership history

GET /api/agents/{id}/ownership/history — full chain of custody: who held ownership, when, transfer reason, who accepted.

6. Ownerless guard

The system must prevent agents from entering an ownerless state: - Force-assign requires admin privilege and is logged as an admin action - Deprovisioning an owner (user deactivation) triggers a pending transfer to the org admin if no transfer is in flight


Data Model

CREATE TABLE agent_ownership_transfers (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    agent_id UUID NOT NULL REFERENCES agents(id),
    from_owner_id TEXT NOT NULL,
    to_owner_id TEXT NOT NULL,
    status TEXT NOT NULL DEFAULT 'pending',  -- pending / accepted / rejected / cancelled / expired / force_assigned
    reason TEXT,
    initiated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    effective_at TIMESTAMPTZ,
    resolved_at TIMESTAMPTZ,
    resolved_by TEXT,
    audit_hash TEXT  -- chained into audit log integrity
);

Acceptance Criteria

  • POST /api/agents/{id}/ownership/transfer creates a pending transfer and notifies incoming owner
  • Incoming owner must explicitly accept before owner_id is updated
  • All transfer events (initiate, accept, reject, cancel, expire, force-assign) written to audit log
  • GET /api/agents/{id}/ownership/history returns full chain of custody
  • Pending transfers older than the configured window escalate to admin
  • Admin force-assign is available but logged as a distinct action
  • No agent can have a null owner_id after FR-002 and FR-004 are deployed