Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .corp-harness/site.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"schema": "corporate-site-site/v1",
"site_id": "corpos",
"corporate_program": "corpos-autonomous-company",
"corporate_handoff_sha256": "0953f106918b8ffe1a61e30f3a69afbd0498bd44df695ff6b3c15c4b6915af68",
"corporate_program": "corpos-autonomous-company-r2",
"corporate_handoff_sha256": "15052d5905e7e73687465dbf6d1d801d31105a7c7abfd33ea50ceb2652dcbeb6",
"verify_argv": ["./scripts/harness/verify.sh"],
"adversarial_argv": ["./scripts/harness/adversarial.sh"]
}
12 changes: 8 additions & 4 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,12 @@
# ── HTTP ─────────────────────────────────────────────────────────────
# PORT=3000

# ── Model providers (optional; SimulationProvider used in CI) ────────
# ── Model providers ──────────────────────────────────────────────────
# Default is SimulationProvider (health.mode=simulation).
# Live HttpLLMProvider only when BOTH are set:
# CORPOS_ALLOW_LIVE=1
# OPENROUTER_API_KEY=...
# OPENROUTER_API_KEY alone does NOT switch agents to live reasoning.
# CORPOS_ALLOW_LIVE=0
# OPENROUTER_API_KEY=
# OPENROUTER_MODEL=openrouter/owl-alpha
# ANTHROPIC_API_KEY=
# OPENAI_API_KEY=
# OPENROUTER_MODEL=openai/gpt-4o-mini
5 changes: 3 additions & 2 deletions CONTEXT.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# Project context

CorpOS is the open reference implementation of an autonomous company:
firm model + work contracts + control plane. Simulation-first.
firm model + work contracts + control plane + governance plane (July 2026).

Workspaces: `packages/core`, `packages/mcp-knowledge`, `apps/api`, `apps/console`.

Commands: `npm run dev`, `npm test`, `npm run scenario`, `npm run audit:verify`,
`./scripts/harness/verify.sh`, `./scripts/harness/adversarial.sh`.

Site id: `corpos`. Program: `corpos-autonomous-company`.
Site id: `corpos`. Program: `corpos-autonomous-company-r2`.
Live LLM requires `CORPOS_ALLOW_LIVE=1` and `OPENROUTER_API_KEY`.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
# CorpOS

> Reference implementation of an **autonomous company** — firm model, work contracts, and a policy-gated control plane.
> Reference implementation of an **autonomous company** — firm model, work contracts, policy-gated control plane, and a July 2026 **governance plane** (PDP/PEP, three-layer authz, OTel GenAI, OWASP ASI / NIST RMF crosswalk).

[![CI](https://github.com/SafetyMP/CorpOS/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/SafetyMP/CorpOS/actions/workflows/ci.yml)
[![CodeQL](https://github.com/SafetyMP/CorpOS/actions/workflows/codeql.yml/badge.svg?branch=main)](https://github.com/SafetyMP/CorpOS/actions/workflows/codeql.yml)
[![OpenSSF Scorecard](https://github.com/SafetyMP/CorpOS/actions/workflows/scorecard.yml/badge.svg?branch=main)](https://scorecard.dev/viewer/?uri=github.com/SafetyMP/CorpOS)
[![License: Apache-2.0](https://img.shields.io/github/license/SafetyMP/CorpOS)](LICENSE)
[![Node](https://img.shields.io/badge/node-%E2%89%A522-339933?logo=node.js&logoColor=white)](#quick-start)

CorpOS shows how a firm operates when department agents do most work and **humans govern by exception**. Autonomy is earned from evidence, not granted in prompts.
CorpOS shows how a firm operates when department agents do most work and **humans govern by exception** (Approve / Reject / Kill in the ops console). Autonomy is earned from evidence, not granted in prompts. Interop protocols (MCP) are transport; **community governance (G1–G6)** lives in the firm.

> **Scope:** Reference architecture and runnable demo — **not** a production-hardened SaaS. See [SECURITY.md](SECURITY.md).

Read the thesis: [`docs/future-of-the-firm.md`](docs/future-of-the-firm.md).
Default mode is **simulation** (`SimulationProvider`) for deterministic CI. Live LLM (`HttpLLMProvider`) only when `CORPOS_ALLOW_LIVE=1` and `OPENROUTER_API_KEY` are set — `/api/health.mode` never lies.

Read the thesis: [`docs/future-of-the-firm.md`](docs/future-of-the-firm.md). Governance crosswalk: [`docs/governance-crosswalk.md`](docs/governance-crosswalk.md). AIBOM: [`docs/aibom.json`](docs/aibom.json).

**Jump to:** [Demo](#demo) · [Quick start](#quick-start) · [Architecture](#architecture) · [Contributing](CONTRIBUTING.md) · [Security](SECURITY.md) · [ADRs](docs/adr/)

Expand Down
90 changes: 81 additions & 9 deletions apps/api/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,16 @@ import {
decideException,
departments,
exceptions,
listSpans,
resolveProvider,
runCompanyDay,
traces,
type Company,
} from "@corpos/core";
import { eq } from "drizzle-orm";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";

function requireAuth(c: { req: { header: (n: string) => string | undefined } }): boolean {
if (process.env.CORPOS_MODE !== "shared") return true;
Expand All @@ -24,15 +29,27 @@ function requireAuth(c: { req: { header: (n: string) => string | undefined } }):
return header === `Bearer ${expected}`;
}

export function buildApp(company: Company): Hono {
function loadAibom(): unknown {
const candidates = [
path.resolve(process.cwd(), "docs/aibom.json"),
path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../../docs/aibom.json"),
];
for (const p of candidates) {
if (fs.existsSync(p)) return JSON.parse(fs.readFileSync(p, "utf8"));
}
return { error: "aibom missing" };
}

export function buildApp(company: Company, mode: "simulation" | "live" = "simulation"): Hono {
const app = new Hono();
app.use("*", cors());

app.get("/api/health", (c) =>
c.json({
ok: true,
mode: process.env.OPENROUTER_API_KEY ? "live" : "simulation",
mode,
product: "autonomous-company-reference",
provider: mode === "live" ? "HttpLLMProvider" : "SimulationProvider",
}),
);

Expand All @@ -57,9 +74,19 @@ export function buildApp(company: Company): Hono {

app.post("/api/exceptions/:id/decide", async (c) => {
if (!requireAuth(c)) return c.json({ error: "dashboard authentication required" }, 401);
const body = await c.req.json<{ decision: "approved" | "rejected"; by?: string }>();
await decideException(company, c.req.param("id"), body.decision, body.by ?? "operator");
return c.json({ ok: true });
const body = await c.req.json<{
decision: "approved" | "rejected";
by?: string;
dissentReason?: string;
}>();
const out = await decideException(
company,
c.req.param("id"),
body.decision,
body.by ?? "operator",
body.dissentReason,
);
return c.json({ ok: true, ...out });
});

app.post("/api/kill", async (c) => {
Expand All @@ -70,11 +97,51 @@ export function buildApp(company: Company): Hono {
});

app.post("/api/company-day", async (c) => {
const { result, company: ephemeral } = await runCompanyDay({ dbPath: ":memory:" });
ephemeral.close();
let autoApproveException = true;
try {
const body = await c.req.json<{ autoApproveException?: boolean }>();
if (body.autoApproveException === false) autoApproveException = false;
} catch {
/* empty body */
}
const { result } = await runCompanyDay({ company, autoApproveException });
return c.json(result);
});

app.get("/api/governance", async (c) => {
const aibom = loadAibom();
const spans = listSpans().slice(-50);
const recentDenies = (await company.audit.verify()).ok;
const ctrl = (
await company.db.select().from(controlState).where(eq(controlState.id, "global"))
)[0];
return c.json({
aibom,
spans,
auditOk: recentDenies,
killed: Boolean(ctrl?.killed),
asiControls: {
ASI01: "untrusted KB/CRM boundary",
ASI02: "fail-closed gateway + draft/settle",
ASI03: "three-layer authz",
ASI04: "AIBOM + policy bundle hash",
ASI05: "no shell/eval tools registered",
ASI06: "untrusted memory/context flags",
ASI07: "handoff envelopes with depth/origin",
ASI08: "capital/kill/depth caps",
ASI09: "L3+ exception HITL",
ASI10: "kill switch + trust demotion",
},
nistRmf: {
GOVERN: "policy PDP/PEP + enforcement modes",
MAP: "AIBOM inventory",
MEASURE: "OTel GenAI spans + trust ledger",
MANAGE: "exceptions, kill, compensators",
},
note: "Crosswalk is pedagogical; not a certification claim.",
});
});

app.get("/api/traces/:taskId", async (c) => {
const row = (await company.db.select().from(traces)).find(
(t) => t.taskId === c.req.param("taskId"),
Expand Down Expand Up @@ -119,6 +186,11 @@ export function buildApp(company: Company): Hono {
return app;
}

export async function createDefaultCompany(): Promise<Company> {
return createCompany({ dbPath: process.env.CORPOS_DB });
export async function createDefaultCompany(): Promise<{
company: Company;
mode: "simulation" | "live";
}> {
const { mode } = resolveProvider();
const company = await createCompany({ dbPath: process.env.CORPOS_DB });
return { company, mode };
}
6 changes: 3 additions & 3 deletions apps/api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ if (scenario) {
process.exit(result.ok ? 0 : 1);
}

const company = await createDefaultCompany();
const app = buildApp(company);
const { company, mode } = await createDefaultCompany();
const app = buildApp(company, mode);

const here = path.dirname(fileURLToPath(import.meta.url));
const consoleDist = path.resolve(here, "../../console/dist");
app.use("/*", serveStatic({ root: consoleDist }));

const port = Number(process.env.PORT ?? 3000);
serve({ fetch: app.fetch, port }, () => {
console.log(`CorpOS ops console on http://localhost:${port}`);
console.log(`CorpOS ops console on http://localhost:${port} (mode=${mode})`);
});
Loading
Loading