# Agent Firm Protocol — Product Specification

**Service schema:** `delx/agent-firm/v1`
**Receipt schema:** `delx/agent-firm-receipt/v1`
**Primary object:** `Firm`
**Current stage:** `private_beta`
**Custody:** none; budgets and payout splits are metadata
**Normative implementation:** `runtime/agent-firm.mjs`

## Purpose

Agent Firm Protocol records a temporary, single-purpose organization with a
human owner, goal, named roles, member assignments, milestone payout metadata,
human veto events, and dissolution report.

Version 1 is a **single-purpose organizational receipt ledger with human-owner
controls**. It is not a legal entity, employer, partnership, DAO, multisig,
treasury, liability shield, or autonomous company.

## Institutional model

The product aims to represent the layer an ordinary orchestrator does not:

- why the organization exists;
- who is the accountable human principal;
- which roles may be filled;
- which milestones were declared complete;
- how a hypothetical payout is attributed;
- when a human veto occurred;
- when the organization ceased operating.

The current implementation records those facts, enforces invitation/member
lifecycle and bounded proposal votes, and tracks a non-custodial budget debit
envelope. It does not grant executable capabilities or bind agents legally.

## Actors and trust model

| Actor | v1 action | Current trust limitation |
| --- | --- | --- |
| Human owner | Incorporates, invites, amends, records milestones, vetoes, dissolves, cleans up | Private HTTP mode binds owner to an Ed25519 principal and tenant. |
| Agent member | Accepts an invitation, joins one named role, resigns | Private HTTP mode binds member to an Ed25519 principal; invitation enforcement is enabled for authenticated incorporation. |
| Operator | Hosts state and signer | Trusted local operator with one-writer assumption. |
| Consumer | Reads active/all firm projections | Private HTTP mode requires `economic:read`; sensitive fields need `economic:private`. |

## Lifecycle

```mermaid
stateDiagram-v2
    [*] --> active: incorporate
    active --> active: invite/accept/join
    active --> active: propose/vote
    active --> active: amend/dispute
    active --> active: complete_milestone
    active --> active: veto
    active --> dissolved: dissolve
    dissolved --> [*]
```

| State | Meaning | Terminal |
| --- | --- | --- |
| `active` | Membership, milestones, vetoes, and dissolution are available according to route rules. | No |
| `dissolved` | Final report is private-scope metadata; no later mutation is accepted. | Yes |
| `disputed` | Dispute reason is recorded; cleanup remains available. | No |

There is no draft, recruiting, paused, insolvent, failed, merged, or reactivated
state. Amendment and dispute are recorded transitions on the active object.

## Authority matrix

| Action | v1 actor check | Production interpretation |
| --- | --- | --- |
| Incorporate | Owner principal and tenant in private HTTP mode | Creates metadata; does not prove a legal entity. |
| Invite/accept/join | Owner creates invitation; member accepts matching invitation | Binds membership in this ledger; no external credential grant. |
| Complete milestone | Owner principal + evidence in private HTTP mode | Records non-custodial milestone/debit metadata. |
| Veto/amend/dispute | Owner/member policy by transition | Records event; does not cancel an external action automatically. |
| Dissolve/cleanup | Owner principal; cleanup revokes pending invitations | Ends the ledger object, not a legal entity or external execution. |

Public beta still requires a live deployment, managed keys, capability grants,
and a policy engine for every external transition.

## Object model

### Incorporation input

| Field | Type | Constraint |
| --- | --- | --- |
| `human_owner_id` | string | Required, non-empty, at most 256 bytes. |
| `goal` | string | Required, non-empty, at most 4,096 bytes. |
| `budget_usdc` | decimal string | Required, positive metadata, up to six fractional digits. |
| `roles` | string array | Required, non-empty; each role unique and at most 64 bytes. |
| `vote_rule` | enum | Optional; defaults to `human_veto_only`. |
| `bylaws` | string | Optional; default retains absolute human veto; at most 8,192 bytes. |
| `idempotency_key` | string | Required, non-empty, at most 256 bytes. |

### Vote-rule truth

Accepted values are `human_veto_only`, `majority`, and `unanimous`.

`human_veto_only` remains a veto-only mode. `majority` and `unanimous` enable
bounded proposal/vote state transitions over current members (owner is not part
of the member electorate); no vote executes an external action, and no
signature, abstention timeout, or legal governance effect is implied.

### Public Firm projection

The private projection includes goal, budget metadata, roles, vote-rule metadata,
bylaws, owner hash, member hashes/roles, milestone evidence hashes, invitation
and proposal summaries, veto count, cleanup/dispute state, timestamps, and
receipt count. Ordinary private lists redact goal, bylaws, milestone titles,
dispute reason, and final report.

Raw owner/member IDs are persisted. Goal, bylaws, milestone titles, and final
report may contain sensitive operational information and are not safe for a
public unauthenticated list.

## Operations and invariants

### Incorporate

`POST /api/v1/economic/firms/incorporate`

Creates an `active` Firm, emits `firm_incorporated`, and scopes idempotency to
`incorporate:<sha256(tenant_id)>:<sha256(human_owner_id)>:<idempotency_key>`.

Budget is not reserved, held, reconciled, or compared with external funds. An
optional `budget_debit_usdc` only updates the bounded local
`budget_spent_usdc` metadata counter.

### Join

`POST /api/v1/economic/firms/join`

Preconditions:

- Firm is active;
- role exists and is not already filled;
- raw `agent_id` has not joined the same Firm.
- when `enforce_invitations=true`, a matching pending invitation is required;
  authenticated HTTP incorporation enables this flag.

Postconditions:

- appends one member;
- emits `agent_joined` with hashed agent ID and role.

Each role can have exactly one member in v1. One member can hold only one role
because repeated raw agent IDs are rejected. `/firms/invite`, `/firms/accept`,
`/firms/revoke-invitation`, `/firms/resign`, and `/firms/remove-member` cover the
bounded member lifecycle; they do not grant capabilities outside the ledger.

### Complete milestone

`POST /api/v1/economic/firms/milestone`

Preconditions:

- Firm is active;
- supplied owner string hash matches;
- `milestone_id` is new;
- payout split is non-empty;
- every entry references a current raw member ID;
- every `bps` is an integer from 0 through 10,000;
- entries sum exactly to 10,000;
- private HTTP mode requires non-empty `evidence` and may include bounded
  `budget_debit_usdc` without moving funds.

Postconditions:

- appends an immutable milestone record;
- emits `milestone_completed` with hashed member IDs and bps.

The implementation rejects duplicate payout members and rejects a debit beyond
the declared budget. Consumers must still treat the split and debit as
non-custodial metadata. No money moves.

### Amend, propose, vote, dispute, and cleanup

`POST /firms/amend` changes bounded goal/bylaws/vote metadata under owner policy.
`POST /firms/propose` records an action digest and proposer; `POST /firms/vote`
records one yes/no vote per member and closes majority/unanimous proposals at
their deterministic quorum. These votes do not execute an action.
`POST /firms/dispute` records a member/owner dispute and
`POST /firms/cleanup` revokes pending invitations after a terminal state.

### Veto

`POST /api/v1/economic/firms/veto`

Requires an active Firm and matching owner string, appends a reason, keeps the
Firm active, and emits `human_veto`. The public Firm exposes only `veto_count`,
while raw reasons remain in stored state and receipts.

A veto does not identify or cancel an external proposal/action in v1. It is an
event receipt, not an execution lock.

### Dissolve

`POST /api/v1/economic/firms/dissolve`

Requires the matching owner string and active state. It records a final report
up to 16,384 bytes, sets `dissolved`, and emits `firm_dissolved` with report hash,
member/milestone counts, and the latest payout split.

The private-scope response and projection include the final report; ordinary
lists redact it.
Dissolution does not revoke external credentials, cancel jobs, settle funds,
notify members, or dissolve a legal entity.

### List

- `GET /api/v1/economic/firms` returns active Firms.
- `GET /api/v1/economic/firms/all` returns active and dissolved Firms.
- Authenticated lists are tenant-filtered, paginated, and expose private text
  only with `economic:private`.

## Idempotency

Every mutation requires a key. Identical scoped replay returns the stored
response; conflicting reuse returns HTTP 409 `idempotency_conflict`.

Scope choices prevent ordinary duplicate joins, votes, and milestones. Private
HTTP adds authenticated tenancy; invitation acceptance replay is stable by
invitation/object scope. Retention limits and cross-instance serialization
remain deployment controls.

## Receipt chain

| Mutation | Receipt kind |
| --- | --- |
| Incorporate | `firm_incorporated` |
| Invite | `agent_invited` |
| Accept | `agent_joined` (accept is an invitation-bound join) |
| Join | `agent_joined` |
| Amend | `firm_amended` |
| Proposal/vote | `proposal_opened` / `proposal_voted` |
| Milestone | `milestone_completed` |
| Veto | `human_veto` |
| Dispute/cleanup | `firm_disputed` / `firm_cleanup_completed` |
| Resign/remove | `member_resigned` / `member_removed` |
| Dissolve | `firm_dissolved` |

Receipts prove what this operator recorded in a hash-linked sequence.
Authenticated HTTP uses the configured Ed25519 signer; the demo signer is
limited to direct local fixtures. See [API Reference](API_REFERENCE.md#receipt-contract).

## Errors

Product-specific error codes include:

`firm_not_found`, `firm_not_active`, `owner_denied`, `invalid_roles`,
`invalid_role`, `role_taken`, `already_member`, `invalid_vote_rule`,
`invalid_split`, `milestone_exists`, `invitation_required`,
`invitation_not_found`, `invitation_not_pending`, `invitation_agent_denied`,
`proposal_not_found`, `proposal_not_open`, `vote_duplicate`, `budget_exceeded`,
`cleanup_not_ready`, and `firm_not_disputable`.

Common errors are defined in [API Reference](API_REFERENCE.md#error-contract).

## Required semantics before an autonomous-firm claim

1. live managed deployment and authenticated owner/member identity;
2. explicit role capabilities and external credential revocation;
3. proposals and enforced vote rules where advertised;
4. budget reservations, spend mandates, and auditable settlement through an
   external approved rail;
5. task and deliverable evidence, not only milestone declarations;
6. replacement, failure, and external task completion paths;
7. cross-product linkage to capacity and judgment with atomic authorization;
8. credential revocation and resource cleanup on dissolution;
9. jurisdiction-specific legal review before legal-entity or employment claims;
10. privacy-safe private/public views and retention controls.

## Composition

The Firm is the intended institutional root: Futures can reserve role capacity
and Judgment can authorize consequential actions. Version 1 stores no
`future_request_id`, `matter_id`, mandate, capability, or transactional
cross-link. The composition is architectural direction, not shipped behavior.
