mcp-agent-broker 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,31 +1,77 @@
1
1
  # MCP Agent Broker
2
2
 
3
- The Model Context Protocol (MCP) Agent Broker is a centralized service designed to orchestrate and manage autonomous coding agents.
3
+ The Model Context Protocol (MCP) Agent Broker is a centralized service designed to orchestrate and manage autonomous coding agents.
4
+
5
+ It acts as a middleware that securely maps AI Agents (Principals) to tasks, enforcing distributed locks (Fencing Tokens) so multiple agents don't step on each other's toes when working on the same repository.
4
6
 
5
7
  ## Features
6
8
  * **MCP Server Integration:** Natively implements the MCP SDK via Server-Sent Events (SSE).
9
+ * **Secure Authentication:** Validates API keys against securely hashed database records.
7
10
  * **Distributed Locking:** Safely hand off tasks between implementer agents and reviewer agents using fencing tokens.
8
11
  * **Transactional Outbox:** Guaranteed delivery of domain events without dual-write issues.
9
12
  * **Event Relay:** Real-time event broadcasting over Redis Pub/Sub.
10
13
 
11
- ## Prerequisites
12
- You will need Docker installed to run the dependencies:
13
- * PostgreSQL
14
- * Redis
15
- * MinIO
14
+ ---
15
+
16
+ ## Getting Started
16
17
 
17
- ## Usage
18
+ ### 1. Start the Infrastructure
19
+ The broker requires PostgreSQL and Redis. We provide a `docker-compose.yml` to spin up everything (including the broker itself) instantly.
18
20
 
19
- 1. Start the infrastructure:
20
21
  ```bash
21
22
  docker compose up -d
22
23
  ```
24
+ *Note: If you are doing local development, you can run `docker compose up -d postgres redis` and then run the app locally using `npm run dev`.*
25
+
26
+ ### 2. Generate an API Key
27
+ To connect to the broker, your Agent must have an API Key. We provide a seed script to generate your first Workspace, Principal, and API Key.
23
28
 
24
- 2. Run the server:
25
29
  ```bash
26
- npm start
30
+ npx tsx seed_api_key.ts
27
31
  ```
28
- The server will bind to `http://localhost:3000/mcp/sse`.
32
+ *Take note of the API Key (e.g. `test-api-key-123`) output by the terminal.*
33
+
34
+ ### 3. Connect your Agents
35
+ Point your MCP-compatible client to the broker's endpoint and pass your API key as a Bearer token.
36
+
37
+ **Programmatic Example (TypeScript):**
38
+ ```typescript
39
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
40
+ import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
41
+
42
+ const transport = new SSEClientTransport(new URL("http://localhost:3000/mcp/sse"), {
43
+ eventSourceInit: {
44
+ headers: { Authorization: "Bearer test-api-key-123" }
45
+ } as any,
46
+ requestInit: {
47
+ headers: { Authorization: "Bearer test-api-key-123" }
48
+ }
49
+ });
50
+
51
+ const client = new Client({ name: "my-agent", version: "1.0.0" }, { capabilities: {} });
52
+ await client.connect(transport);
53
+
54
+ // Register your agent!
55
+ await client.request({
56
+ method: "tools/call",
57
+ params: {
58
+ name: "register_agent",
59
+ arguments: {
60
+ workspaceId: "00000000-0000-0000-0000-000000000000",
61
+ name: "My Implementer Agent",
62
+ version: "1.0",
63
+ capabilities: ["execute"]
64
+ }
65
+ }
66
+ }, CallToolResultSchema);
67
+ ```
68
+
69
+ ### 4. Listen to Real-Time Events
70
+ Any downstream CI dashboard or review agent can connect to the Redis server and subscribe to `workspace:{workspace_id}:events` to see real-time updates as tasks are created and leases are acquired!
71
+
72
+ ---
29
73
 
30
- 3. Connect your Agents:
31
- Point your MCP-compatible client (like Claude Code) to `http://localhost:3000/mcp/sse`. They can now invoke tools like `register_agent`, `create_task`, and `acquire_task_lease`!
74
+ ## Future Improvements for Contributors
75
+ * Implement standard OAuth2 / OIDC for user-facing dashboards.
76
+ * Extend the `Review Module` to enforce reviewer sign-offs before pushing to main branches.
77
+ * Implement the Fencing Token check in a Git Hook or CI/CD layer.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.idempotencyRecords = exports.outbox = exports.events = exports.leases = exports.taskAttempts = exports.tasks = exports.agentInstances = exports.agentDefinitions = exports.workspaces = exports.leaseTypeEnum = exports.leaseStatusEnum = exports.agentStatusEnum = exports.workspaceStatusEnum = exports.taskStateEnum = void 0;
3
+ exports.idempotencyRecords = exports.outbox = exports.events = exports.leases = exports.taskAttempts = exports.tasks = exports.agentInstances = exports.agentDefinitions = exports.apiKeys = exports.principals = exports.workspaces = exports.leaseTypeEnum = exports.leaseStatusEnum = exports.agentStatusEnum = exports.workspaceStatusEnum = exports.taskStateEnum = void 0;
4
4
  const pg_core_1 = require("drizzle-orm/pg-core");
5
5
  exports.taskStateEnum = (0, pg_core_1.pgEnum)('task_state', [
6
6
  'draft', 'ready', 'leased', 'running', 'blocked',
@@ -19,6 +19,16 @@ exports.workspaces = (0, pg_core_1.pgTable)('workspaces', {
19
19
  createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).notNull().defaultNow(),
20
20
  updatedAt: (0, pg_core_1.timestamp)('updated_at', { withTimezone: true }).notNull().defaultNow(),
21
21
  });
22
+ exports.principals = (0, pg_core_1.pgTable)('principals', {
23
+ id: (0, pg_core_1.uuid)('id').primaryKey(),
24
+ name: (0, pg_core_1.text)('name').notNull(),
25
+ createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).notNull().defaultNow(),
26
+ });
27
+ exports.apiKeys = (0, pg_core_1.pgTable)('api_keys', {
28
+ keyHash: (0, pg_core_1.text)('key_hash').primaryKey(),
29
+ principalId: (0, pg_core_1.uuid)('principal_id').notNull().references(() => exports.principals.id),
30
+ createdAt: (0, pg_core_1.timestamp)('created_at', { withTimezone: true }).notNull().defaultNow(),
31
+ });
22
32
  exports.agentDefinitions = (0, pg_core_1.pgTable)('agent_definitions', {
23
33
  id: (0, pg_core_1.uuid)('id').primaryKey(),
24
34
  name: (0, pg_core_1.text)('name').notNull(),
@@ -17,7 +17,7 @@ class AgentService {
17
17
  return workspace;
18
18
  }
19
19
  async registerAgent(params) {
20
- const workspace = await this.ensureDefaultWorkspace();
20
+ const workspace = await this.ensureDefaultWorkspace(); // Fallback logic or just rely on params
21
21
  // 1. Create or find agent definition
22
22
  let [definition] = await index_js_1.db.select().from(schema_js_1.agentDefinitions)
23
23
  .where((0, drizzle_orm_1.eq)(schema_js_1.agentDefinitions.name, params.name))
@@ -44,8 +44,8 @@ class AgentService {
44
44
  const [instance] = await index_js_1.db.insert(schema_js_1.agentInstances).values({
45
45
  id: (0, crypto_1.randomUUID)(),
46
46
  agentDefinitionId: definition.id,
47
- workspaceId: workspace.id,
48
- principalId: (0, crypto_1.randomUUID)(), // Mocking principal ID for Phase 0
47
+ workspaceId: params.workspaceId,
48
+ principalId: params.principalId,
49
49
  capabilitiesJson: params.capabilities,
50
50
  status: "connected",
51
51
  }).returning();
@@ -4,6 +4,10 @@ exports.setupMcpTransport = setupMcpTransport;
4
4
  const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
5
5
  const sse_js_1 = require("@modelcontextprotocol/sdk/server/sse.js");
6
6
  const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
7
+ const crypto_1 = require("crypto");
8
+ const drizzle_orm_1 = require("drizzle-orm");
9
+ const index_js_2 = require("../../infrastructure/db/index.js");
10
+ const schema_js_1 = require("../../infrastructure/db/schema.js");
7
11
  const agent_service_js_1 = require("../../modules/identity/agent.service.js");
8
12
  const task_service_js_1 = require("../../modules/task/task.service.js");
9
13
  const lease_service_js_1 = require("../../modules/lease/lease.service.js");
@@ -11,6 +15,19 @@ async function setupMcpTransport(fastify) {
11
15
  // Store transports for the POST /mcp/message route
12
16
  const transports = new Map();
13
17
  fastify.get("/mcp/sse", async (request, reply) => {
18
+ const authHeader = request.headers.authorization;
19
+ if (!authHeader || !authHeader.startsWith('Bearer ')) {
20
+ reply.status(401).send({ error: "Missing or invalid Authorization header" });
21
+ return;
22
+ }
23
+ const token = authHeader.split(' ')[1];
24
+ const keyHash = (0, crypto_1.createHash)('sha256').update(token).digest('hex');
25
+ const [apiKeyRecord] = await index_js_2.db.select().from(schema_js_1.apiKeys).where((0, drizzle_orm_1.eq)(schema_js_1.apiKeys.keyHash, keyHash));
26
+ if (!apiKeyRecord) {
27
+ reply.status(401).send({ error: "Invalid API Key" });
28
+ return;
29
+ }
30
+ const principalId = apiKeyRecord.principalId;
14
31
  // Hijack the Fastify response so we can stream SSE headers manually
15
32
  reply.hijack();
16
33
  const transport = new sse_js_1.SSEServerTransport("/mcp/message", reply.raw);
@@ -75,7 +92,9 @@ async function setupMcpTransport(fastify) {
75
92
  if (req.params.name === "register_agent") {
76
93
  try {
77
94
  const args = req.params.arguments;
78
- const instance = await agent_service_js_1.agentService.registerAgent({
95
+ const agentInstance = await agent_service_js_1.agentService.registerAgent({
96
+ workspaceId: args.workspaceId || (0, crypto_1.randomUUID)(), // Temporary fallback
97
+ principalId: principalId,
79
98
  name: args.name,
80
99
  version: args.version,
81
100
  capabilities: args.capabilities,
@@ -84,7 +103,7 @@ async function setupMcpTransport(fastify) {
84
103
  content: [
85
104
  {
86
105
  type: "text",
87
- text: `Agent registered successfully! Instance ID: ${instance.id}`
106
+ text: `Agent registered successfully! Instance ID: ${agentInstance.id}`
88
107
  }
89
108
  ]
90
109
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-agent-broker",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "main": "dist/app.js",
5
5
  "bin": {
6
6
  "mcp-agent-broker": "dist/app.js"
@@ -9,7 +9,7 @@
9
9
  "build": "tsc",
10
10
  "start": "node dist/app.js",
11
11
  "dev": "tsx watch src/app.ts",
12
- "test": "echo \"Error: no test specified\" && exit 1"
12
+ "test": "vitest run"
13
13
  },
14
14
  "keywords": [],
15
15
  "author": "",
@@ -30,6 +30,7 @@
30
30
  "@types/pg": "^8.20.0",
31
31
  "drizzle-kit": "^0.31.10",
32
32
  "tsx": "^4.23.1",
33
- "typescript": "^7.0.2"
33
+ "typescript": "^7.0.2",
34
+ "vitest": "^4.1.10"
34
35
  }
35
36
  }
@@ -0,0 +1,30 @@
1
+ import { db } from './src/infrastructure/db/index.js';
2
+ import { principals, apiKeys, workspaces } from './src/infrastructure/db/schema.js';
3
+ import { randomUUID, createHash } from 'crypto';
4
+
5
+ async function seed() {
6
+ const principalId = randomUUID();
7
+ const rawKey = 'test-api-key-123';
8
+ const keyHash = createHash('sha256').update(rawKey).digest('hex');
9
+
10
+ const workspaceId = '00000000-0000-0000-0000-000000000000';
11
+ await db.insert(workspaces).values({
12
+ id: workspaceId,
13
+ name: 'Test Workspace',
14
+ }).onConflictDoNothing();
15
+
16
+ await db.insert(principals).values({
17
+ id: principalId,
18
+ name: 'Test Service Account',
19
+ }).onConflictDoNothing();
20
+
21
+ await db.insert(apiKeys).values({
22
+ keyHash,
23
+ principalId,
24
+ });
25
+
26
+ console.log('Seeded principal:', principalId);
27
+ console.log('API Key:', rawKey);
28
+ process.exit(0);
29
+ }
30
+ seed().catch(console.error);
@@ -1,41 +0,0 @@
1
- services:
2
- postgres:
3
- image: postgres:16-alpine
4
- container_name: mab-postgres
5
- environment:
6
- POSTGRES_USER: mab_user
7
- POSTGRES_PASSWORD: mab_password
8
- POSTGRES_DB: mab_db
9
- ports:
10
- - "5433:5432"
11
- volumes:
12
- - postgres_data:/var/lib/postgresql/data
13
- restart: unless-stopped
14
-
15
- redis:
16
- image: redis:7-alpine
17
- container_name: mab-redis
18
- ports:
19
- - "6379:6379"
20
- volumes:
21
- - redis_data:/data
22
- restart: unless-stopped
23
-
24
- minio:
25
- image: minio/minio:latest
26
- container_name: mab-minio
27
- environment:
28
- MINIO_ROOT_USER: mab_admin
29
- MINIO_ROOT_PASSWORD: mab_password123
30
- command: server /data --console-address ":9001"
31
- ports:
32
- - "9000:9000"
33
- - "9001:9001"
34
- volumes:
35
- - minio_data:/data
36
- restart: unless-stopped
37
-
38
- volumes:
39
- postgres_data:
40
- redis_data:
41
- minio_data:
package/drizzle.config.ts DELETED
@@ -1,10 +0,0 @@
1
- import { defineConfig } from "drizzle-kit";
2
-
3
- export default defineConfig({
4
- schema: "./src/infrastructure/db/schema.ts",
5
- out: "./src/infrastructure/db/migrations",
6
- dialect: "postgresql",
7
- dbCredentials: {
8
- url: process.env.DATABASE_URL || "postgres://mab_user:mab_password@127.0.0.1:5433/mab_db"
9
- }
10
- });
package/src/app.ts DELETED
@@ -1,31 +0,0 @@
1
- #!/usr/bin/env node
2
- import Fastify from 'fastify';
3
- import { setupMcpTransport } from './transport/mcp/index.js';
4
- import { db } from './infrastructure/db/index.js';
5
- import { outboxRelay } from './workers/outbox-relay.js';
6
-
7
- const fastify = Fastify({
8
- logger: true
9
- });
10
-
11
- fastify.get('/health', async (request, reply) => {
12
- return { status: 'ok', service: 'mcp-agent-broker' };
13
- });
14
-
15
- // Start the server
16
- const start = async () => {
17
- try {
18
- await setupMcpTransport(fastify);
19
-
20
- // Start background workers
21
- outboxRelay.start();
22
-
23
- await fastify.listen({ port: 3000, host: '0.0.0.0' });
24
- fastify.log.info(`Server listening on ${fastify.server.address()}`);
25
- } catch (err) {
26
- fastify.log.error(err);
27
- process.exit(1);
28
- }
29
- };
30
-
31
- start();
@@ -1,10 +0,0 @@
1
- import { drizzle } from 'drizzle-orm/node-postgres';
2
- import { Pool } from 'pg';
3
- import * as schema from './schema';
4
-
5
- // Use a connection pool for the Fastify app
6
- const pool = new Pool({
7
- connectionString: process.env.DATABASE_URL || "postgres://mab_user:mab_password@127.0.0.1:5433/mab_db"
8
- });
9
-
10
- export const db = drizzle(pool, { schema });
@@ -1,103 +0,0 @@
1
- CREATE TYPE "public"."lease_status" AS ENUM('active', 'released', 'expired', 'revoked');--> statement-breakpoint
2
- CREATE TYPE "public"."lease_type" AS ENUM('execution', 'review');--> statement-breakpoint
3
- CREATE TYPE "public"."task_state" AS ENUM('draft', 'ready', 'leased', 'running', 'blocked', 'awaiting_review', 'changes_requested', 'completed', 'failed', 'canceled', 'superseded');--> statement-breakpoint
4
- CREATE TABLE "events" (
5
- "event_seq" bigserial PRIMARY KEY NOT NULL,
6
- "event_id" uuid NOT NULL,
7
- "event_type" text NOT NULL,
8
- "event_version" integer NOT NULL,
9
- "workspace_id" uuid NOT NULL,
10
- "aggregate_type" text NOT NULL,
11
- "aggregate_id" uuid NOT NULL,
12
- "aggregate_version" bigint NOT NULL,
13
- "actor_type" text NOT NULL,
14
- "actor_id" uuid,
15
- "correlation_id" uuid NOT NULL,
16
- "causation_id" uuid,
17
- "payload" jsonb NOT NULL,
18
- "occurred_at" timestamp with time zone DEFAULT now() NOT NULL,
19
- CONSTRAINT "events_event_id_unique" UNIQUE("event_id"),
20
- CONSTRAINT "events_aggregate_type_aggregate_id_aggregate_version_unique" UNIQUE("aggregate_type","aggregate_id","aggregate_version")
21
- );
22
- --> statement-breakpoint
23
- CREATE TABLE "idempotency_records" (
24
- "principal_id" uuid NOT NULL,
25
- "operation" text NOT NULL,
26
- "idempotency_key" text NOT NULL,
27
- "request_hash" text NOT NULL,
28
- "response_json" jsonb,
29
- "response_status" text NOT NULL,
30
- "resource_id" uuid,
31
- "created_at" timestamp with time zone DEFAULT now() NOT NULL,
32
- "expires_at" timestamp with time zone NOT NULL,
33
- CONSTRAINT "idempotency_records_principal_id_operation_idempotency_key_pk" PRIMARY KEY("principal_id","operation","idempotency_key")
34
- );
35
- --> statement-breakpoint
36
- CREATE TABLE "leases" (
37
- "id" uuid PRIMARY KEY NOT NULL,
38
- "workspace_id" uuid NOT NULL,
39
- "task_id" uuid NOT NULL,
40
- "task_attempt_id" uuid,
41
- "agent_instance_id" uuid NOT NULL,
42
- "lease_type" "lease_type" NOT NULL,
43
- "status" "lease_status" NOT NULL,
44
- "fencing_token" bigint NOT NULL,
45
- "acquired_at" timestamp with time zone NOT NULL,
46
- "renewed_at" timestamp with time zone NOT NULL,
47
- "expires_at" timestamp with time zone NOT NULL,
48
- "released_at" timestamp with time zone,
49
- CONSTRAINT "leases_task_id_lease_type_fencing_token_unique" UNIQUE("task_id","lease_type","fencing_token")
50
- );
51
- --> statement-breakpoint
52
- CREATE TABLE "outbox" (
53
- "event_id" uuid PRIMARY KEY NOT NULL,
54
- "available_at" timestamp with time zone DEFAULT now() NOT NULL,
55
- "attempts" integer DEFAULT 0 NOT NULL,
56
- "locked_by" text,
57
- "locked_until" timestamp with time zone,
58
- "published_at" timestamp with time zone,
59
- "last_error" text,
60
- "created_at" timestamp with time zone DEFAULT now() NOT NULL
61
- );
62
- --> statement-breakpoint
63
- CREATE TABLE "task_attempts" (
64
- "id" uuid PRIMARY KEY NOT NULL,
65
- "workspace_id" uuid NOT NULL,
66
- "task_id" uuid NOT NULL,
67
- "attempt_number" integer NOT NULL,
68
- "agent_instance_id" uuid NOT NULL,
69
- "status" text NOT NULL,
70
- "started_at" timestamp with time zone,
71
- "ended_at" timestamp with time zone,
72
- "outcome" text,
73
- CONSTRAINT "task_attempts_task_id_attempt_number_unique" UNIQUE("task_id","attempt_number")
74
- );
75
- --> statement-breakpoint
76
- CREATE TABLE "tasks" (
77
- "id" uuid PRIMARY KEY NOT NULL,
78
- "workspace_id" uuid NOT NULL,
79
- "repository_id" uuid NOT NULL,
80
- "session_id" uuid,
81
- "objective" text NOT NULL,
82
- "description" text DEFAULT '' NOT NULL,
83
- "acceptance_criteria" jsonb DEFAULT '[]' NOT NULL,
84
- "constraints" jsonb DEFAULT '[]' NOT NULL,
85
- "priority" smallint DEFAULT 3 NOT NULL,
86
- "state" "task_state" DEFAULT 'draft' NOT NULL,
87
- "base_commit_sha" text,
88
- "active_execution_lease_id" uuid,
89
- "active_review_lease_id" uuid,
90
- "next_fencing_token" bigint DEFAULT 0 NOT NULL,
91
- "version" bigint DEFAULT 1 NOT NULL,
92
- "created_by" uuid NOT NULL,
93
- "created_at" timestamp with time zone DEFAULT now() NOT NULL,
94
- "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
95
- "completed_at" timestamp with time zone
96
- );
97
- --> statement-breakpoint
98
- ALTER TABLE "leases" ADD CONSTRAINT "leases_task_id_tasks_id_fk" FOREIGN KEY ("task_id") REFERENCES "public"."tasks"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
99
- ALTER TABLE "leases" ADD CONSTRAINT "leases_task_attempt_id_task_attempts_id_fk" FOREIGN KEY ("task_attempt_id") REFERENCES "public"."task_attempts"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
100
- ALTER TABLE "outbox" ADD CONSTRAINT "outbox_event_id_events_event_id_fk" FOREIGN KEY ("event_id") REFERENCES "public"."events"("event_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
101
- ALTER TABLE "task_attempts" ADD CONSTRAINT "task_attempts_task_id_tasks_id_fk" FOREIGN KEY ("task_id") REFERENCES "public"."tasks"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
102
- CREATE INDEX "events_workspace_cursor" ON "events" USING btree ("workspace_id","event_seq");--> statement-breakpoint
103
- CREATE INDEX "idempotency_expiry" ON "idempotency_records" USING btree ("expires_at");
@@ -1,36 +0,0 @@
1
- CREATE TYPE "public"."agent_status" AS ENUM('connected', 'degraded', 'disconnected', 'revoked');--> statement-breakpoint
2
- CREATE TYPE "public"."workspace_status" AS ENUM('active', 'archived');--> statement-breakpoint
3
- CREATE TABLE "agent_definitions" (
4
- "id" uuid PRIMARY KEY NOT NULL,
5
- "name" text NOT NULL,
6
- "vendor" text,
7
- "version" text NOT NULL,
8
- "integration_type" text,
9
- "created_at" timestamp with time zone DEFAULT now() NOT NULL,
10
- "updated_at" timestamp with time zone DEFAULT now() NOT NULL
11
- );
12
- --> statement-breakpoint
13
- CREATE TABLE "agent_instances" (
14
- "id" uuid PRIMARY KEY NOT NULL,
15
- "agent_definition_id" uuid NOT NULL,
16
- "workspace_id" uuid NOT NULL,
17
- "principal_id" uuid NOT NULL,
18
- "status" "agent_status" DEFAULT 'connected' NOT NULL,
19
- "capabilities_json" jsonb DEFAULT '[]' NOT NULL,
20
- "last_heartbeat_at" timestamp with time zone,
21
- "revoked_at" timestamp with time zone,
22
- "created_at" timestamp with time zone DEFAULT now() NOT NULL,
23
- "updated_at" timestamp with time zone DEFAULT now() NOT NULL
24
- );
25
- --> statement-breakpoint
26
- CREATE TABLE "workspaces" (
27
- "id" uuid PRIMARY KEY NOT NULL,
28
- "organization_id" uuid,
29
- "name" text NOT NULL,
30
- "status" "workspace_status" DEFAULT 'active' NOT NULL,
31
- "created_at" timestamp with time zone DEFAULT now() NOT NULL,
32
- "updated_at" timestamp with time zone DEFAULT now() NOT NULL
33
- );
34
- --> statement-breakpoint
35
- ALTER TABLE "agent_instances" ADD CONSTRAINT "agent_instances_agent_definition_id_agent_definitions_id_fk" FOREIGN KEY ("agent_definition_id") REFERENCES "public"."agent_definitions"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
36
- ALTER TABLE "agent_instances" ADD CONSTRAINT "agent_instances_workspace_id_workspaces_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspaces"("id") ON DELETE no action ON UPDATE no action;