Skip to content

Approvals, receipts, and verification ​

This page is for operators and anyone deciding whether to trust agent work in Company OS: when work pauses for a human, what evidence an action leaves, and how to check that evidence yourself.

Related pages: CLI reference, connecting agents, Console guide.

In the commands below, companyos is the CLI binary. The CLI's own usage text prints the same commands as company-os. Every command accepts --company-id ID to name the company explicitly.

1. Why approvals exist ​

Some actions are costly or hard to undo, so Company OS pauses them until a person decides. A fluent agent answer is not proof that an action was allowed or that it worked.

Which actions pause ​

The production write policy (PRODUCTION_READINESS_POLICIES.md) states the default: agents work in dry-run shadow mode. They may inspect, plan, and validate, but they may not perform production writes until a workflow has an explicit mutation policy and an evidence trail.

Human approval is required for actions that are non-reversible, high-value, customer-facing, or permission-changing. These are blocked until an explicit policy exists: delete, bulk update, external payment or purchase, customer email send, credential or permission change, and cross-tenant data move.

Other pause points in the sources:

  • Agent-proposed email replies stay pending_approval for a separate human verifier; Company OS does not send them (services/company-os-mcp-server/README.md).
  • Learned autonomy (HITL_AND_SKILL_PROMOTION_MOAT_CONTRACT.md): a missing or incomplete decision shape always pauses. A learned auto preference cannot authorize a different resource, scope, value band, policy revision, or a higher risk band. legacy-unbound preference rows never authorize autonomous proceed.

Who may decide ​

  • company_agents.role is a job title; only owner and admin carry authority (src/company_os/governance_doc.rs).
  • Some actions are member-reserved: only an active human owner may take them; an admin gets 403 member_reserved_owner_required and a refusal receipt. They include granting owner or admin, changing an owner, changing a model agent's adapter_type or model config, granting a root delegation, minting a token that acts as another owner, lifting a malfunction flag, and deciding an approval whose request_payload.required_authority is "owner" (MEMBER_RESERVED_AUTHORITY_AND_AGENT_AUTO_SUSPEND.md). That document says no approval producer declares that value yet.
  • "Active human owner": an active owner company_agents row with no model adapter, or an active owner company_human_memberships row.
  • An approval cannot be used if it was decided by its own requester (approvalBindingProblem in services/company-os-mcp-server/src/client.ts).

What is not approval ​

  • A model-supplied confirm=true is not human approval (services/company-os-mcp-server/README.md).
  • --confirm on a CLI command is not an approval by itself. API principal, idempotency, spend, settlement, and Gate checks still apply (CLI usage text in src/cli.ts).
  • MCP strict mode (the default) returns approval_required for mutating tools unless the caller passes confirm=true after explicit operator approval. permissive is for trusted local automation only. The remote Grok bridge always uses strict.
  • In bound approval mode (defined in client.ts, not described in the package README), a mutating call must carry one approval id that is approved, has a deciding principal, was not decided by its requester, is unconsumed, and is unexpired. The id is cleared after one use. A model-driven MCP session cannot decide approvals in this mode; a human uses the Console or CLI.
  • The desktop app does not manufacture approval locally (COMPANY_OS_START_HERE.md).

2. Lifecycle of an approval ​

text
requested -> shown -> decided (approved | rejected) -> executed, or not
  1. Requested. Policy pauses the work. An approval row is created with risk domain, risk level, action type, subject, requested_by, optional expires_at, and a request_payload.
  2. Shown. It appears in the company's inbox. Rows with a decision envelope carry subject, presentation, and request digests plus a presentation_revision, describing what the approver was shown.
  3. Decided. A person approves or rejects; decided_by and decided_at are recorded.
  4. Executed or not. In bound mode an approved, unconsumed, unexpired approval authorizes one action. A rejected, expired, or consumed one cannot.

Digest-bound decisions ​

For a row with a decision envelope, the CLI reads the authoritative inbox first, then submits your decision together with the server-stored subject_digest, presentation_digest, request_digest, and presentation_revision. It never accepts a caller-invented approval projection. If the stored envelope is incomplete, the command fails rather than guessing.

legacy_unverifiable applies to approval rows, not receipts. It classifies rows created before the decision envelope existed. Their decision_contract reports verification_supported: false because no digests exist to bind a decision to; Company OS cannot prove what the approver was shown. The CLI sends digests only for rows with an envelope, so a decision on such a row is not digest-bound by the CLI. Whether the server accepts it is not stated in the sources.

Where to decide ​

PlaceHow
ConsoleOpen Approvals (see the Console guide).
CLIcompanyos approval approve ID --confirm or companyos approval reject ID --confirm
Tern palette"Open Company OS approvals waiting on you" only lists pending approvals; decide in the Console or CLI (integrations/tern-companyos/README.md).

CLI commands ​

bash
# List the inbox (filter by state, page with --limit and --offset)
companyos approvals --status pending

# Inspect one approval before deciding
companyos approval show APPROVAL_ID

# Decide (all three forms require --confirm)
companyos approval approve APPROVAL_ID --reason "why" --confirm
companyos approval reject  APPROVAL_ID --reason "why" --confirm
companyos approval decide  APPROVAL_ID --decision approved|rejected --reason "why" --confirm

--reason is optional. --dry-run runs the real handler path, including the authoritative reads, and prints the planned request instead of sending it; no --confirm is needed. Run approval show first: the decision binds to the digests the server stored, not to what you remember seeing.

3. Receipts ​

A receipt records what happened during important work, read from the company ledger and scoped to the authenticated company. Refusals are receipted too, never silent (src/company_os/governance_doc.rs).

Receipt id families ​

Ids are family-qualified so receipt show resolves the exact tenant-scoped row.

PrefixMeaningVerifiable
terminal:Terminal receipt for a task or run outcome. Carries sequence, terminal_generation, previous_hash, receipt_hash.Yes
run_event:Run execution event.No
governance:Governance event, such as an approval decision or a refusal.No
spend:Spend event.No

The list code also emits review_delivery:; the package README names only the four above.

List, show, verify ​

bash
companyos receipt list
companyos receipt show   terminal:RECEIPT_UUID
companyos receipt verify terminal:RECEIPT_UUID
companyos verify task TASK_ID
companyos verify run  RUN_ID

What receipt verify checks ​

For a terminal: receipt, the server recomputes the database-owned canonical hash and checks both chain linkage and the stored company head. The response (hsm.company_os.receipt_verification.v1) includes:

  • verified: true only if hash_matches (stored equals recomputed hash), previous_link_matches (previous_hash equals the expected previous hash), and company_chain.valid all hold.
  • evidence: sequence, stored_hash, computed_hash, previous_hash, expected_previous_hash, and company_chain (valid, checked_count, first_bad_sequence, expected_head_hash).

What verification_supported=false means ​

Timeline families without a digest contract (run_event:, governance:, spend:) return verification_supported=false. They are readable evidence, but they never claim to be verified. The MCP presentation layer reports this as not_available ("This result family does not expose a verification contract"). not_available is not a failure and it is not a pass.

A successful tool call is never treated as verification. The result envelope (hsm.company_os.result.v1) says not_available unless the canonical payload explicitly reports verification.

verify task and verify run ​

These replay the immutable lifecycle history and compare it with the live projection, using the same authenticated company principal.

  • Task responses (hsm.company_os.task_verification.v1) include verified, has_drift, drift_kind, and aggregate_versions for state, metadata, context, operational, and run snapshot, plus redacted evidence for each.
  • Run responses (hsm.company_os.run_verification.v1) include verified, has_drift, drift_kind, aggregate_version, and evidence.lifecycle.
  • verified is !has_drift. Missing history or a replay failure is never reported as verified.

4. Auto-suspension and safety rails for agents ​

Source: MEMBER_RESERVED_AUTHORITY_AND_AGENT_AUTO_SUSPEND.md. Rules live in src/company_os/agent_oversight.rs.

A model-driven principal is suspended, fail closed, while either condition holds:

  • oversight_vacant: the company has no active human owner.

  • agent_malfunction_flagged: an unlifted row in company_agent_malfunction_flags names the agent.

  • Suspension is derived on every read. It is never written into agent status. A read failure refuses.

  • Human principals are never suspended, so an owner can always act.

  • Every refusal is 423 Locked with code: "agent_suspended" and the reason_code above.

  • Enforcement points and their receipts:

Check pointReceipt
Worker dispatchtask_events.dispatch_refused
Tool calls inside a run (live read for the run's agent)task-trail tool_denied
Principal-token mutationsgovernance_events.company_mutation_refused
Signed run-capability authorizegovernance_events.run_capability_action_refused
  • Settlement of an already-authorized run-capability action stays open, so its outcome is still recorded.

Flagging and lifting:

text
GET  /api/company/companies/:company_id/agents/:agent_id/suspension
POST /api/company/companies/:company_id/agents/:agent_id/suspension        {"reason": "..."}
POST /api/company/companies/:company_id/agents/:agent_id/suspension/lift   {"reason": "..."}
  • Any active human owner or admin may flag a model-backed agent. Repeating the flag while one is open is idempotent. Flags cannot be raised on human rows.
  • Only an owner may lift. A lifted flag is final; a new incident is a new flag.
  • Flag rows are evidence: the database refuses any rewrite except the single lift, and the runtime role has no DELETE.

Related rails:

  • Delegation enforcement is on by default; a live principal token is not authority without a scoped delegation from an owner or admin (src/company_os/governance_doc.rs).
  • With no active human owner, an active human admin may install a human (adapter-less) agent as owner, and nothing else. With no usable owner or admin credential, use the existing root-transport owner recovery.

5. FAQ ​

Is --confirm the same as approving? No. --confirm acknowledges a mutating CLI command, and the other server checks still apply. To decide an approval, use companyos approval approve|reject ID --confirm as an authenticated principal.

Can an agent approve its own request? Not through the checked paths. A model-supplied confirm=true is not human approval; in bound mode a model-driven MCP session cannot decide approvals; an approval decided by its requester cannot authorize an action; and required_authority: "owner" approvals refuse an admin.

A receipt says verification_supported=false. Is something wrong? No. That family (run_event:, governance:, spend:) has no digest contract. Use the terminal: receipt, verify task, or verify run instead.

receipt verify returned verified: false. What now? Read evidence: hash_matches, previous_link_matches, and company_chain.first_bad_sequence show which check failed. The sources do not describe a repair procedure.

Why was my agent refused with 423?code: "agent_suspended": either oversight_vacant (no active human owner) or agent_malfunction_flagged. Restore an owner, or have an owner lift the flag.

Company OS documentation — built with VitePress