{"id":50734105,"url":"https://github.com/openma-ai/open-managed-agents","last_synced_at":"2026-06-27T14:00:33.868Z","repository":{"id":350598043,"uuid":"1206555053","full_name":"openma-ai/open-managed-agents","owner":"openma-ai","description":"Open-source Claude Managed Agents API implementation and self-hosted Claude Tag-style agent runtime. Drop-in compatible; runs on Cloudflare Workers/Durable Objects or Node.js. Apache 2.0.","archived":false,"fork":false,"pushed_at":"2026-06-26T05:23:50.000Z","size":9776,"stargazers_count":184,"open_issues_count":20,"forks_count":16,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-26T06:10:36.511Z","etag":null,"topics":["agent-framework","agent-infrastructure","agent-platform","ai-agents","anthropic-api","anthropic-managed-agents","byok","claude-api","claude-code","claude-managed-agents","claude-tag","claude-tag-alternative","cloudflare","durable-objects","managed-agents","mcp","open-managed-agents","open-source-agents","self-hosted","self-hosted-agents"],"latest_commit_sha":null,"homepage":"https://openma.dev","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/openma-ai.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":"NOTICE","maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-04-10T03:03:36.000Z","updated_at":"2026-06-26T05:22:58.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/openma-ai/open-managed-agents","commit_stats":null,"previous_names":["hrhrng/open-managed-agents","open-ma/open-managed-agents","openma-ai/open-managed-agents"],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/openma-ai/open-managed-agents","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openma-ai%2Fopen-managed-agents","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openma-ai%2Fopen-managed-agents/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openma-ai%2Fopen-managed-agents/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openma-ai%2Fopen-managed-agents/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/openma-ai","download_url":"https://codeload.github.com/openma-ai/open-managed-agents/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openma-ai%2Fopen-managed-agents/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34855826,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-27T02:00:06.362Z","response_time":126,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agent-framework","agent-infrastructure","agent-platform","ai-agents","anthropic-api","anthropic-managed-agents","byok","claude-api","claude-code","claude-managed-agents","claude-tag","claude-tag-alternative","cloudflare","durable-objects","managed-agents","mcp","open-managed-agents","open-source-agents","self-hosted","self-hosted-agents"],"created_at":"2026-06-10T12:00:29.380Z","updated_at":"2026-06-27T14:00:33.825Z","avatar_url":"https://github.com/openma-ai.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"logo.svg\" alt=\"openma\" height=\"80\" /\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Cloudflare-Workers-F38020?logo=cloudflare\u0026logoColor=white\" alt=\"Cloudflare Workers\" /\u003e\n  \u003cimg src=\"https://img.shields.io/badge/TypeScript-5.8-3178C6?logo=typescript\u0026logoColor=white\" alt=\"TypeScript\" /\u003e\n  \u003cimg src=\"https://img.shields.io/badge/License-Apache_2.0-blue.svg\" alt=\"Apache 2.0 License\" /\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Tests-passing-brightgreen\" alt=\"Tests\" /\u003e\n  \u003cimg src=\"https://img.shields.io/badge/API-Anthropic%20Compatible-blueviolet\" alt=\"Anthropic Compatible\" /\u003e\n\u003c/p\u003e\n\n# Open Managed Agents\n\n**Open-source alternative to Claude Managed Agents** — and a foundation for open-source, self-hosted Claude Tag-style agents.\n\n🌐 **[openma.dev](https://openma.dev)** · 📖 **[docs.openma.dev](https://docs.openma.dev)** · 💬 **[github.com/openma-ai/open-managed-agents](https://github.com/openma-ai/open-managed-agents)**\n\nWrite a harness. Deploy. The platform runs it — with sessions, sandboxes, tools, memory, vaults, Slack/GitHub/Linear integrations, and crash recovery out of the box. Drop-in compatible with the Claude Managed Agents API; runs on Cloudflare Workers + Durable Objects, or `docker compose up` on your own box.\n\nUse Open Managed Agents when you want:\n\n- A self-hosted Claude Managed Agents API implementation.\n- An open-source, self-hosted Claude Tag-style workflow with BYOK model credentials.\n- MCP, private tools, encrypted vaults, and durable sessions under your own deployment boundary.\n\nCompare: [Claude Tag alternative](https://openma.dev/claude-tag-alternative/) · [Open-source Claude Tag](https://openma.dev/claude-tag-open-source/) · [Self-hosted Claude Tag](https://openma.dev/self-hosted-claude-tag/)\n\n---\n\n## Two ways to run OMA\n\nThe same harness, business logic, and event-log model run on both. Pick the\none that matches your hosting story:\n\n| | **Self-host (Node)** | **Cloudflare** |\n|---|---|---|\n| Where it lives | Your VPS / Mac / Docker host / fly.io / your k8s | Cloudflare Workers + DO + Containers |\n| Storage | SQLite or Postgres + local FS | D1 + KV + R2 |\n| Sandbox | LocalSubprocess / LiteBox / Daytona / E2B / BoxRun | Cloudflare Sandbox (Containers) |\n| Time to running | `docker compose up` (~2 min) | wrangler deploy (~10 min once configured) |\n| Best for | OSS users, on-prem, no CF account, data-resident deploys | Edge scale, no host management, already on CF |\n\n**Same SDK.** Same `/v1/agents` / `/v1/sessions` API. Same Console UI. Same\ncrash-recovery semantics. Switch between them by changing env vars, not code.\n\n---\n\n## Quick start: self-host (Docker)\n\n```bash\ngit clone https://github.com/openma-ai/open-managed-agents.git\ncd open-managed-agents\ncp .env.example .env\n\n# Two secrets are required before first boot — both generated locally:\n#   BETTER_AUTH_SECRET   — signs Console sessions\n#   PLATFORM_ROOT_SECRET — encrypts credentials, model-card API keys, integration tokens at rest\n#                          (lose it and every encrypted row is unreadable — back it up)\n$EDITOR .env\n# BETTER_AUTH_SECRET=$(openssl rand -hex 32)\n# PLATFORM_ROOT_SECRET=$(openssl rand -base64 32)\n#\n# Optional: ANTHROPIC_API_KEY lets the first agent run without a Model Card.\n# In production, add a Model Card per tenant from the Console instead.\n\n# SQLite + LocalSubprocess sandbox (default — fastest path)\ndocker compose up -d\n\n# Or Postgres backend\n# docker compose -f docker-compose.postgres.yml up -d\n\ncurl localhost:8787/health\n# → {\"status\":\"ok\",\"backends\":{\"db\":\"sqlite ...\"}, ...}\n\nopen http://localhost:8787   # Console UI on the same port\n```\n\nSmoke test the harness end-to-end:\n\n```bash\nAID=$(curl -s -X POST localhost:8787/v1/agents -H 'content-type: application/json' \\\n  -d '{\"name\":\"hello\",\"model\":\"claude-sonnet-4-6\",\"tools\":[{\"type\":\"agent_toolset_20260401\"}]}' | jq -r .id)\n\nSID=$(curl -s -X POST localhost:8787/v1/sessions -H 'content-type: application/json' \\\n  -d \"{\\\"agent\\\":\\\"$AID\\\"}\" | jq -r .id)\n\ncurl -s -X POST localhost:8787/v1/sessions/$SID/events -H 'content-type: application/json' \\\n  -d '{\"events\":[{\"type\":\"user.message\",\"content\":[{\"type\":\"text\",\"text\":\"Run: uname -a\"}]}]}'\n```\n\nFull self-host guide (sandbox modes, Postgres, BoxRun, vault sidecar,\nConsole UI, operator gotchas): **[docs.openma.dev/self-host/overview](https://docs.openma.dev/self-host/overview/)**\n\n---\n\n## Quick start: Cloudflare deploy\n\nRequires [Workers Paid plan](https://developers.cloudflare.com/workers/platform/pricing/) (for Durable Objects + Containers).\n\n```bash\ngit clone https://github.com/openma-ai/open-managed-agents.git\ncd open-managed-agents\npnpm install\n\n# Local dev (no CF account needed) — wrangler dev with simulators\ncp .dev.vars.example .dev.vars \u0026\u0026 $EDITOR .dev.vars\n# Same two-secret setup as Docker — PLATFORM_ROOT_SECRET is required to start\npnpm dev\n# API   → http://localhost:8787\n# Console → http://localhost:5173\n\n# Deploy\nnpx wrangler login\nnpx wrangler kv namespace create CONFIG_KV   # paste id into wrangler.jsonc\n\n# Required secrets (paste each when prompted)\nnpx wrangler secret put BETTER_AUTH_SECRET    # openssl rand -hex 32\nnpx wrangler secret put PLATFORM_ROOT_SECRET  # openssl rand -base64 32 — back this up\nnpx wrangler secret put API_KEY               # initial bootstrap key for the REST API\n\n# Optional — only if you want a tenant-less default LLM (otherwise add a Model Card in the Console)\n# npx wrangler secret put ANTHROPIC_API_KEY\n\nnpm run deploy\n# → https://openma.dev (or https://managed-agents.\u003cyour-subdomain\u003e.workers.dev for a personal deploy)\n```\n\nWhat gets deployed:\n\n| Component | What it does |\n|---|---|\n| **Main Worker** | API routes — agents, sessions, environments, vaults, memory, files |\n| **Agent Worker** | SessionDO + harness + sandbox per environment |\n| **KV Namespace** | Config storage for agents, environments, credentials |\n| **R2 Bucket** | Workspace file persistence across container restarts |\n\n### Create your first agent\n\nThe smoke test above works against any deployment. For the Console-driven flow (Model Cards, vaults, integrations) see **[docs.openma.dev/quickstart](https://docs.openma.dev/quickstart)**. The minimal API equivalent:\n\n```bash\nBASE=http://localhost:8787   # or your deployed URL\nKEY=dev-test-key             # whatever you set as API_KEY\n\nAGENT=$(curl -s $BASE/v1/agents \\\n  -H \"x-api-key: $KEY\" -H \"content-type: application/json\" \\\n  -d '{\n    \"name\": \"Coder\",\n    \"model\": \"claude-sonnet-4-6\",\n    \"system\": \"You are a helpful coding assistant.\",\n    \"tools\": [{ \"type\": \"agent_toolset_20260401\" }]\n  }' | jq -r .id)\n\nSESSION=$(curl -s $BASE/v1/sessions \\\n  -H \"x-api-key: $KEY\" -H \"content-type: application/json\" \\\n  -d \"{\\\"agent\\\":\\\"$AGENT\\\"}\" | jq -r .id)\n\n# Send a turn AND stream the reply token-by-token in one shot\ncurl -N -X POST $BASE/v1/sessions/$SESSION/messages \\\n  -H \"x-api-key: $KEY\" -H \"content-type: application/json\" \\\n  -d '{\"content\":\"Write a Python script that fetches HN top stories\"}'\n```\n\nFor long-lived sessions use `GET /v1/sessions/$SESSION/events/stream` — replays history on connect, never closes.\n\n---\n\n## Architecture\n\nA **meta-harness** is not an agent — it's the platform that runs agents. It defines stable interfaces for everything an agent needs, and stays out of the way of the agent loop:\n\n```\n┌─────────────────────────────────────────────────────────┐\n│  Harness (the brain — your code)                        │\n│  - Reads events, builds context, calls the model        │\n│  - Decides HOW: caching, compaction, tool delivery      │\n│  - Stateless: crash → rebuild from event log → resume   │\n├─────────────────────────────────────────────────────────┤\n│  Meta-Harness (the platform — SessionDO)                │\n│  - Prepares WHAT is available: tools, skills, history   │\n│  - Manages lifecycle: sandbox, events, WebSocket        │\n│  - Crash recovery, credential isolation, usage tracking │\n├─────────────────────────────────────────────────────────┤\n│  Infrastructure (Cloudflare or Node self-host)          │\n│  - Event log: Durable-Object SQLite (CF) or SQLite/Pg   │\n│  - Sandbox: CF Containers / subprocess / LiteBox / E2B  │\n│  - Storage: KV + R2 (CF) or local FS (self-host)        │\n└─────────────────────────────────────────────────────────┘\n```\n\n**The platform prepares _what_ is available. The harness decides _how_ to deliver it to the model.**\n\n| Platform manages | Harness decides |\n|---|---|\n| Event log persistence (SQLite) | Context engineering (filtering, ordering) |\n| Sandbox lifecycle (containers) | Caching strategy (cache breakpoints) |\n| Tool registration (built-in + MCP) | Compaction strategy (when to compress) |\n| WebSocket broadcast | Retry strategy (backoff, transient detection) |\n| Crash recovery | Stop conditions (max steps, completion signals) |\n| Credential isolation (vaults) | System prompt construction |\n| Memory (vector search) | Tool delivery (all at once vs. progressive) |\n\n---\n\n## Write a Harness\n\nThe default harness works out of the box. When you need custom behavior — different caching, compaction, context engineering — write your own:\n\n```typescript\n// my-harness.ts\nimport { defineHarness, generateText, stepCountIs } from \"@open-managed-agents/sdk\";\n\nexport default defineHarness({\n  name: \"research\",\n\n  async run(ctx) {\n    let messages = ctx.runtime.history.getMessages();\n\n    // Your context engineering\n    messages = keepOnly(messages, [\"web_search\", \"web_fetch\"]);\n\n    // Your caching strategy\n    markLastN(messages, 3, { cacheControl: \"ephemeral\" });\n\n    // Your loop — tools, sandbox, broadcast are platform-provided\n    const result = await generateText({\n      model: ctx.model,\n      system: ctx.systemPrompt,\n      messages,\n      tools: ctx.tools,\n      stopWhen: stepCountIs(50),\n      onStepFinish: async ({ text }) =\u003e {\n        if (text) ctx.runtime.broadcast({\n          type: \"agent.message\",\n          content: [{ type: \"text\", text }],\n        });\n      },\n    });\n\n    await ctx.runtime.reportUsage?.(result.usage.inputTokens, result.usage.outputTokens);\n  },\n});\n```\n\nDeploy it:\n\n```bash\noma deploy --harness my-harness.ts --agent agent_abc123\n```\n\nThe harness is bundled into the agent worker at build time. Your code runs in the same isolate as SessionDO — direct access to the event log, sandbox, and WebSocket broadcast. No RPC, no serialization boundary.\n\n---\n\n## API\n\nCompatible with the [Claude Managed Agents API](https://docs.anthropic.com/en/docs/agents/managed-agents). Same endpoints, same event types, works with existing SDKs.\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eAgents\u003c/strong\u003e — Create and manage agent configurations\u003c/summary\u003e\n\n```http\nPOST   /v1/agents                          # Create agent\nGET    /v1/agents                          # List agents\nGET    /v1/agents/:id                      # Get agent\nPUT    /v1/agents/:id                      # Update agent\nDELETE /v1/agents/:id                      # Delete agent\nPOST   /v1/agents/:id/archive             # Archive agent\nGET    /v1/agents/:id/versions            # Version history\nGET    /v1/agents/:id/versions/:version   # Get specific version\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eEnvironments\u003c/strong\u003e — Sandbox execution environments\u003c/summary\u003e\n\n```http\nPOST   /v1/environments                   # Create environment\nGET    /v1/environments                   # List environments\nGET    /v1/environments/:id               # Get environment\nPUT    /v1/environments/:id               # Update environment\nDELETE /v1/environments/:id               # Delete environment\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eSessions\u003c/strong\u003e — Run agent conversations\u003c/summary\u003e\n\n```http\nPOST   /v1/sessions                        # Create session\nGET    /v1/sessions                        # List sessions\nGET    /v1/sessions/:id                    # Get session\nPOST   /v1/sessions/:id                    # Update session\nDELETE /v1/sessions/:id                    # Delete session\nPOST   /v1/sessions/:id/archive           # Archive session\n\nPOST   /v1/sessions/:id/events            # Send events (user messages)\nGET    /v1/sessions/:id/events             # Get events (JSON or SSE)\nGET    /v1/sessions/:id/events/stream      # SSE stream\n\nPOST   /v1/sessions/:id/resources          # Attach resource\nGET    /v1/sessions/:id/resources          # List resources\nDELETE /v1/sessions/:id/resources/:resId   # Remove resource\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eVaults\u003c/strong\u003e — Secure credential storage\u003c/summary\u003e\n\n```http\nPOST   /v1/vaults                          # Create vault\nPOST   /v1/vaults/:id/credentials          # Add credential\nGET    /v1/vaults/:id/credentials          # List (secrets stripped)\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eMemory Stores\u003c/strong\u003e — persistent storage; Claude Managed Agents Memory contract\u003c/summary\u003e\n\nWhen attached to a session, each store is mounted into the sandbox at\n`/mnt/memory/\u003cstore_name\u003e/`. The agent reads and writes it with the\n**standard file tools** (bash/read/write/edit/glob/grep) — there are no\nbespoke `memory_*` tools.\n\nR2 holds the bytes-of-truth (key `\u003cstore_id\u003e/\u003cmemory_path\u003e`); D1 holds the\nindex + audit, kept eventually consistent via R2 Event Notifications →\nCloudflare Queue → Consumer.\n\n```http\nPOST   /v1/memory_stores                                        # Create store\nGET    /v1/memory_stores                                        # List stores\nGET    /v1/memory_stores/:id                                    # Retrieve store\nPOST   /v1/memory_stores/:id/archive                            # Archive (one-way)\nDELETE /v1/memory_stores/:id                                    # Delete store + memories + versions\n\nPOST   /v1/memory_stores/:id/memories                           # Create/upsert memory {path, content, precondition?}\nGET    /v1/memory_stores/:id/memories?path_prefix=\u0026depth=N      # List memories (metadata)\nGET    /v1/memory_stores/:id/memories/:mid                      # Retrieve memory (with content)\nPOST   /v1/memory_stores/:id/memories/:mid                      # Update memory {path?, content?, precondition?}\nDELETE /v1/memory_stores/:id/memories/:mid                      # Delete memory\n\nGET    /v1/memory_stores/:id/memory_versions?memory_id=         # Audit history (newest first)\nGET    /v1/memory_stores/:id/memory_versions/:ver_id            # Single version (with snapshot content)\nPOST   /v1/memory_stores/:id/memory_versions/:ver_id/redact     # Redact prior version (refuses live head)\n```\n\nCAS via `precondition: { type: \"content_sha256\", content_sha256 }`. 100KB\ncap per memory. 30-day version retention with the most-recent version per\nmemory always preserved. Rollback = retrieve a version and write its\ncontent as a new memory revision (no special endpoint).\n\nCLI:\n```bash\noma memory stores create \"User Preferences\"\noma memory write \u003cstore-id\u003e /preferences/formatting.md --content \"Always use tabs.\"\noma memory ls \u003cstore-id\u003e --prefix /preferences/\noma memory versions \u003cstore-id\u003e --memory-id \u003cmem-id\u003e\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eFiles \u0026 Skills\u003c/strong\u003e\u003c/summary\u003e\n\n```http\nPOST   /v1/files                           # Upload file\nGET    /v1/files/:id/content               # Download file\nPOST   /v1/skills                          # Create skill\nGET    /v1/skills                          # List skills\n```\n\n\u003c/details\u003e\n\n---\n\n## Built-in Tools\n\nThe `agent_toolset_20260401` provides:\n\n| Tool | Description |\n|---|---|\n| `bash` | Execute commands in the sandbox |\n| `read` | Read files from sandbox filesystem |\n| `write` | Write/create files (auto-creates directories) |\n| `edit` | Surgical string replacement in files |\n| `glob` | Find files matching a pattern |\n| `grep` | Search file contents with regex |\n| `web_fetch` | URL → markdown via Workers AI; auto-summarized when `agent.aux_model` is set, raw saved to `/workspace/.web/` |\n| `web_search` | Web search via Tavily API (requires `TAVILY_API_KEY`) |\n| `schedule` / `cancel_schedule` / `list_schedules` | Cron-style self-wakeup for long-running agents |\n| `browser` (opt-in) | Headless browser session — navigate, click, screenshot. Opt-in via `tools: [{ name: \"browser\", enabled: true }]` so the default-tool list nudges agents toward cheaper `web_fetch` |\n\nDerived tools are auto-generated based on session config:\n\n| Tool | Source |\n|---|---|\n| `call_agent_*` | Callable Agents (multi-agent delegation) |\n| `mcp__\u003cserver\u003e__\u003ctool\u003e` | MCP Servers (double underscore is the actual separator) |\n\n(Memory Stores do **not** add bespoke tools — agents access them as filesystem\nmounts at `/mnt/memory/\u003cstore_name\u003e/` via the standard file tools above.)\n\n---\n\n## MCP servers\n\nOMA registers any [Model Context Protocol](https://modelcontextprotocol.io) server attached to an agent. Each upstream tool surfaces to the model as `mcp__\u003cserver\u003e__\u003ctool\u003e` (double underscore — copy the name exactly). Up to 20 servers per agent.\n\n| Transport | When to use | How |\n|---|---|---|\n| HTTP / SSE | Hosted MCP servers (Linear, GitHub Copilot, Notion, …) | `{\"type\":\"url\",\"url\":\"https://mcp.linear.app/mcp\"}` |\n| stdio | npm / PyPI MCP packages with no hosted endpoint | `{\"type\":\"stdio\",\"command\":\"uvx\",\"args\":[...],\"port\":8765}` — OMA spawns inside the sandbox container, talks to `127.0.0.1:port/sse` |\n\nCredentials never enter the sandbox; the outbound resolver matches by host and injects at forward time.\n\n| Auth mode | Configured as | Refresh |\n|---|---|---|\n| none | no `authorization_token`, no matching vault credential | n/a |\n| inline bearer | `\"authorization_token\": \"...\"` on the server entry | no |\n| vault static bearer | session vault has a `static_bearer` credential whose `mcp_server_url` matches | no |\n| vault OAuth | session vault has an `mcp_oauth` credential (with `refresh_token` + `token_endpoint`) | yes — on 401 **and 403** (Airtable/Asana/Sentry use 403 for expired tokens), CAS-writes new token to D1, retries once |\n\n```bash\n# Servers attach to the agent (not the session)\ncurl -X PUT $BASE/v1/agents/$AGENT -H \"x-api-key: $KEY\" -H \"content-type: application/json\" \\\n  -d '{\"mcp_servers\":[{\"name\":\"linear\",\"type\":\"url\",\"url\":\"https://mcp.linear.app/mcp\"}]}'\n\n# Bind an OAuth credential via Vault\noma connect linear --vault $VAULT_ID\n```\n\nTool discovery is bounded at 15 s per server; one bad server logs and skips, the rest stay live. Full design: [docs.openma.dev/build/vault-and-mcp](https://docs.openma.dev/build/vault-and-mcp/).\n\n---\n\n## Skills\n\nA skill is a `SKILL.md` plus reference files (templates, schemas, examples). At session start the platform mounts everything under `/home/user/.skills/{name}/` in the sandbox **and inlines the SKILL.md body directly into the system prompt** — no lazy read, no follow-up `read` tool call. Format is compatible with Anthropic's [Claude Code skills](https://github.com/anthropics/skills).\n\nCreate a skill (JSON; files inlined):\n\n```http\nPOST /v1/skills\n{\n  \"files\": [\n    { \"filename\": \"SKILL.md\", \"content\": \"---\\nname: invoice-parser\\ndescription: Parse supplier invoices.\\n---\\n\\n# Steps\\n1. ...\" },\n    { \"filename\": \"schema.json\", \"content\": \"{...}\" }\n  ]\n}\n```\n\nFor large skills with binaries: `POST /v1/skills/upload` multipart with `file=\u003cmy-skill.zip\u003e`.\n\nAttach to an agent with the **object form** — a bare string array silently does not bind:\n\n```json\n{ \"skills\": [{ \"skill_id\": \"skill_abc123\", \"type\": \"custom\" }] }\n```\n\nThe agent's system prompt then receives, at session start:\n\n```text\n\u003csource name=\"skill:skill_abc123\"\u003e\n\u003cskill name=\"invoice-parser\"\u003e\n{full SKILL.md body}\n\u003c/skill\u003e\n\u003c/source\u003e\n```\n\nand the files appear at `/home/user/.skills/invoice-parser/SKILL.md` etc.\n\nFour built-in skills ship ready to attach (no upload): `xlsx`, `pdf`, `docx`, `pptx`. Reference them with `{\"skill_id\":\"builtin_pdf\",\"type\":\"anthropic\"}`.\n\n---\n\n## Vaults \u0026 outbound credentials\n\n**Tools never see your tokens.** When a sandbox makes an HTTP request, an outbound resolver — `oma-vault` sidecar on self-host (mockttp HTTPS proxy with a trusted self-signed CA), the agent worker's `outboundByHost` interceptor on Cloudflare — matches the request hostname against the session's vaults, **strips any inbound `Authorization`/`x-api-key`/`x-goog-api-key`**, injects the real credential, and forwards. A prompt-injected agent has nothing to leak; `env | grep TOKEN` returns nothing inside the sandbox.\n\n```bash\n# Create a vault and add a static bearer bound to api.github.com\nVID=$(curl -sX POST $BASE/v1/vaults -H \"x-api-key: $KEY\" \\\n  -d '{\"name\":\"github-prod\"}' | jq -r .id)\n\ncurl -sX POST $BASE/v1/vaults/$VID/credentials -H \"x-api-key: $KEY\" -d '{\n  \"display_name\": \"gh-pat\",\n  \"auth\": {\n    \"type\": \"static_bearer\",\n    \"token\": \"ghp_xxx\",\n    \"mcp_server_url\": \"https://api.github.com\"\n  }\n}'\n\n# Bind on session create\ncurl -sX POST $BASE/v1/sessions -H \"x-api-key: $KEY\" \\\n  -d \"{\\\"agent\\\":\\\"$AGENT\\\",\\\"vault_ids\\\":[\\\"$VID\\\"]}\"\n\n# Inside the sandbox: curl https://api.github.com/user → 200, Authorization injected at the network layer\n```\n\nThree credential types share one resolver:\n\n| Type | Match by | Refresh |\n|---|---|---|\n| `static_bearer` | request host matches `mcp_server_url` | never |\n| `mcp_oauth` | request host matches `mcp_server_url` | on 401 / 403 via `token_endpoint`, CAS-writes new token to D1 |\n| `cap_cli` | sandbox CLI invocations match `cli_id` in the cap registry (`gh`, `glab`, `aws`, …) | per-CLI |\n\nMax 20 credentials per vault. Each forward emits a structured `op:\"mcp_proxy.forward\"` log. Full design: [`docs/mcp-credential-architecture.md`](docs/mcp-credential-architecture.md), [docs.openma.dev/build/vault-and-mcp](https://docs.openma.dev/build/vault-and-mcp/).\n\n---\n\n## Integrations\n\nPublish an agent into a third-party tool and have it act as a real teammate there — assigned, mentioned, replied to like any other user.\n\n### Linear\n\nMake an agent a member of your Linear workspace with its own identity, avatar, and `@autocomplete` slot. The agent appears in the assignee dropdown, gets pinged on `@mentions`, replies in the Agent panel, and pushes status back to issues it's working on.\n\nTwo install kinds:\n\n| Kind | When to pick | Setup |\n|---|---|---|\n| **`personal_token`** (PAT) | Single workspace, fastest path, no OAuth App | `oma linear install-pat --workspace \u003cslug\u003e --pat \u003clinear-pat\u003e` |\n| **`dedicated`** (OAuth App) | Multi-workspace, proper bot identity, OAuth refresh | Console **Integrations → Linear → Publish agent** (wizard issues per-publication callback + webhook URLs to paste into your own Linear OAuth App at `linear.app/settings/api`) |\n\nThe full agent-side playbook (when to ask the human, how to offer browser automation, exactly what to paste into Linear's form) lives at [`skills/openma/integrations-linear.md`](skills/openma/integrations-linear.md).\n\nPAT-mode autopilot — let the bot pick up unassigned issues by label/state/project:\n\n```bash\noma linear rules create \u003cpub-id\u003e --label triage --state Backlog --project \"Inbox\"\noma linear rules list \u003cpub-id\u003e\noma linear rules delete \u003crule-id\u003e\n```\n\nInspect / manage:\n\n```bash\noma linear list                                       # workspaces\noma linear pubs \u003cinstallation-id\u003e                     # publications (status=live, persona, caps)\noma linear get \u003cpub-id\u003e                               # single publication\noma linear update \u003cpub-id\u003e --caps issue.read,comment.write,issue.update,…\noma linear unpublish \u003cpub-id\u003e\n```\n\nHow it works:\n\n| Piece | What it does |\n|---|---|\n| **Per-publication identity** | `dedicated` registers a per-agent Linear OAuth App; `personal_token` shares the human's PAT (no App registered) |\n| **Inbound webhook** | Linear events become user messages on a session — assigned, `@mention`, comment-mention, new comment in an active thread, **Agent panel** (`agentSessionCreated` / `agentSessionPrompted`, `commentReply` for threaded continuation) |\n| **Outbound MCP** | The agent talks back through `mcp.linear.app/mcp` with its own bearer (PAT or OAuth-refreshed), so writes are attributed to the persona |\n| **Capability gate** | Per-publication allowlist (issues / comments / labels / assignment / triage) limits what the agent can do |\n\nThe Linear integration ships in `packages/linear/` (provider logic, webhook signing, MCP wiring) with thin CF wrappers in `apps/integrations/src/routes/linear/publications.ts`.\n\n### GitHub\n\nGive an agent its own GitHub App with a real bot identity — assignable on issues, requestable as a reviewer on PRs, posts comments under its own `@\u003cslug\u003e[bot]` handle. Each agent is a separate App on github.com (per-publication, not a shared marketplace bot), so credentials and audit trails stay isolated.\n\n```bash\n# (1) Console — humans clicking through a wizard\nIntegrations → GitHub → Publish agent\n\n# (2) CLI — agents driving openma on a user's behalf\noma github bind \u003cagent-id\u003e --env \u003cenv-id\u003e       # → opens one-click GitHub App Manifest flow\noma github handoff \u003cform-token\u003e                 # alt: 7-day URL for an org admin to complete\noma github list\noma github pubs \u003cinstallation-id\u003e\noma github update \u003cpub-id\u003e --caps pr.read,pr.review.write,issue.comment.write,…\noma github unpublish \u003cpub-id\u003e\n```\n\n`bind` returns a `manifestStartUrl`; opening it auto-POSTs an App manifest to `github.com/settings/apps/new` with redirect URL + webhook URL + recommended permissions baked in. After confirming, GitHub redirects through to \"Install on org\" and the publication flips to `live`. Manual fallback: `oma github submit \u003cform-token\u003e --app-id … --private-key-file … --webhook-secret …` if you registered the App by hand.\n\n**Engagement is label-based.** On install OMA auto-creates a label (default: lowercased persona name) in every selected repo. Add the label to any issue/PR to engage the bot for every subsequent activity on that thread; remove the label to mute. `@\u003cslug\u003e[bot]` mention in body or comment is the fallback path (GitHub's `@` autocomplete excludes Bot accounts, so it's plain-text).\n\nHow it works:\n\n| Piece | What it does |\n|---|---|\n| **Per-publication App** | Each agent registers its own GitHub App via Manifest flow; credentials stored encrypted per-publication |\n| **Inbound webhook** | `issues`, `issue_comment`, `pull_request`, `pull_request_review`, `pull_request_review_comment` become user messages on a session (one per `\u003crepo\u003e#\u003cnum\u003e`) |\n| **Outbound MCP** | Agent talks to GitHub's hosted MCP at `api.githubcopilot.com/mcp/` with the installation token; same token also injected as `GITHUB_TOKEN` for sandbox `gh` / `git` |\n| **Token rotation** | 1-hour installation token auto-refreshed via App JWT on every webhook dispatch |\n| **Capability gate** | Per-publication allowlist; destructive ops (`pr.merge`, `repo.branch.delete`, `workflow.dispatch`, `release.create`, `*.delete`) require explicit opt-in |\n\nThe GitHub integration ships in `packages/github/` with thin CF wrappers in `apps/integrations/src/routes/github/`.\n\n### Slack\n\nPublish an agent into a Slack workspace as a dedicated bot — `@mention`able in channels, replies in threads, joins DMs, hosts the AI assistant pane. Per-channel sessions: one running session per `(publication, channel)`, with all events in that channel converging on the same session id.\n\n```bash\n# (1) Console — humans clicking through a wizard\nIntegrations → Slack → Publish agent   # ↑ opens api.slack.com with a pre-filled manifest\n\n# (2) CLI — agents driving openma on a user's behalf\noma slack publish \u003cagent-id\u003e --env \u003cenv-id\u003e    # → returns manifestLaunchUrl + formToken (60 min TTL)\noma slack submit \u003cform-token\u003e --client-id … --client-secret … --signing-secret …\noma slack handoff \u003cform-token\u003e                 # alt: 7-day shareable URL for a workspace admin\noma slack list\noma slack pubs \u003cinstallation-id\u003e\noma slack update \u003cpub-id\u003e --caps message.write,thread.reply,reaction.add,…\noma slack unpublish \u003cpub-id\u003e\n```\n\nThe full agent-side playbook (manifest-flow caveats, `GATEWAY_ORIGIN` HTTPS requirement, what to paste where, MCP toggle probe) lives at [`skills/openma/integrations-slack.md`](skills/openma/integrations-slack.md).\n\nHow it works:\n\n| Piece | What it does |\n|---|---|\n| **Per-publication App** | Each agent registers as its own dedicated Slack App via the \"Create from manifest\" URL flow — own client id, signing secret, bot user; no shared marketplace App |\n| **Inbound webhook** | `app_mention` / DM / thread reply → `direct_invocation` signal; top-level channel post → debounced `channel_scan_armed` (90 s window); reactions on bot-authored messages → `reaction_on_bot_message`; `member_joined`/`member_left_channel` for the bot → `joined_channel` / `session_closed`; `channel_archive` / `channel_unarchive` → close / reopen |\n| **Dual-token outbound** | OAuth v2 yields both bot (`xoxb-`) and user (`xoxp-`) tokens. The `xoxp-` vault binds to `mcp.slack.com/mcp` for typed `mcp__slack__*` tools (search, history, canvases); the `xoxb-` vault binds to `slack.com/api` for `chat.postMessage`, reactions, etc. Bot replies default to in-thread |\n| **Capability gate** | Per-publication allowlist (`message.read/write/update/delete`, `thread.reply`, `reaction.add/remove`, `user.read`, `search.read`, `canvas.write`) |\n| **Resumable install** | Publication-first — the row exists from minute one with callback + webhook URLs baked into the manifest. Mid-flow failures stay resumable from Console (`pending_setup` → `credentials_filled` → `awaiting_install` → `live`) |\n\nThe Slack integration ships in `packages/slack/` with thin CF wrappers in `apps/integrations/src/routes/slack/`.\n\n**Operator setup:** the integrations gateway needs `GATEWAY_ORIGIN` pointing at a publicly-reachable HTTPS host — Slack verifies both the OAuth redirect URL and the Events Request URL before letting an install complete.\n\n---\n\n## Project Structure\n\n```\nopen-managed-agents/\n├── apps/\n│   ├── main/              # API worker (Cloudflare) — Hono routes, auth, rate limiting\n│   ├── main-node/         # API worker (Node self-host) — same routes on Hono/Node\n│   ├── agent/             # Agent worker — SessionDO + harness + sandbox\n│   ├── integrations/      # Integrations gateway — Linear / GitHub / Slack OAuth + webhooks\n│   ├── oma-vault/         # Vault sidecar — outbound auth-header injection (per-host secrets)\n│   ├── console/           # Web dashboard — React + Vite + Tailwind v4\n│   ├── docs/              # Docs site (Astro Starlight) — published to docs.openma.dev\n│   └── web/               # Marketing site (Astro) — published to openma.dev\n├── packages/\n│   ├── cli/                       # `oma` CLI — agent / session / integration commands\n│   ├── sdk/                       # Harness SDK — defineHarness, generateText helpers\n│   ├── api-types/                 # Shared TypeScript types (config schemas, events)\n│   ├── http-routes/               # Public REST route definitions (shared by main + main-node)\n│   ├── session-runtime/           # Harness runtime — event log, broadcast, recovery\n│   ├── sandbox/                   # Sandbox adapters (subprocess / litebox / daytona / e2b / boxrun)\n│   ├── credentials-store/         # Encrypted credentials (AES-GCM under PLATFORM_ROOT_SECRET)\n│   ├── model-cards-store/         # Encrypted model-card API keys\n│   ├── vaults-store/              # Vault definitions + outbound auth wiring\n│   ├── linear/  github/  slack/   # Provider logic (OAuth, webhook signing, MCP wiring)\n│   ├── integrations-core/         # Provider-neutral persistence interfaces\n│   └── integrations-adapters-{cf,node}/  # D1 / KV / Workers + Postgres / FS implementations\n├── docs/                  # Internal design RFCs (not the user-facing site)\n├── test/                  # Unit + integration tests\n└── scripts/               # Deployment + maintenance scripts\n```\n\n---\n\n## Configuration\n\nThe variables that gate boot and at-rest safety:\n\n| Variable | Required | Description |\n|---|---|---|\n| `PLATFORM_ROOT_SECRET` | **Yes** | AES-GCM key for `credentials.auth`, `model_cards.api_key_cipher`, and integration tokens. Workers refuse to start without it. **Back this up** — losing it makes every encrypted row unreadable. Generate with `openssl rand -base64 32`. |\n| `BETTER_AUTH_SECRET` | **Yes** (prod) | better-auth session signing key. Sessions don't survive restart if missing. Generate with `openssl rand -hex 32`. |\n| `API_KEY` | Yes | Bootstrap key for the REST API in dev / first-run. Once the Console is up, prefer per-tenant API keys minted from there. |\n| `INTEGRATIONS_INTERNAL_SECRET` | Yes (if `apps/integrations` runs) | Shared secret between `apps/main` and `apps/integrations`. |\n| `ANTHROPIC_API_KEY` | No | Fallback LLM credential used when a tenant has not added a Model Card. **In production, add a Model Card per tenant from the Console** — the key is encrypted at rest under `PLATFORM_ROOT_SECRET`, scoped to the tenant, and rotatable without redeploy. |\n| `ANTHROPIC_BASE_URL` | No | Override for Anthropic-compatible proxies. |\n| `PUBLIC_BASE_URL` | No (dev) / Yes (prod) | Cookie domain + OAuth redirect base. Defaults to `*` trusted-origins — only safe for local dev. |\n| `SANDBOX_PROVIDER` | No | `subprocess` (default, no isolation), `litebox` (Firecracker), `daytona`, `e2b`, or `boxrun`. Use an isolated backend for untrusted agents. |\n| `TAVILY_API_KEY` | No | Backend for the `web_search` built-in tool. |\n\nFull list (integrations OAuth credentials, Postgres URL, sandbox tunables, memory-bucket config, Google sign-in, etc.) — see **[docs.openma.dev/reference/configuration](https://docs.openma.dev/reference/configuration/)** and `.env.example` / `.dev.vars.example`.\n\n---\n\n## Model Cards\n\nPer-tenant LLM credentials. An agent references one by setting `agent.model = \"\u003cmodel_id\u003e\"` — the worker looks up the card and signs the outbound request with its api_key, base_url, and headers. This is the canonical replacement for the global `ANTHROPIC_API_KEY` env var.\n\nProviders (wire tag → request shape):\n\n| tag | shape | typical use |\n|---|---|---|\n| `ant` | Anthropic `/v1/messages` | Claude on `api.anthropic.com` |\n| `ant-compatible` | Anthropic shape, custom `base_url` | Bedrock proxy, self-hosted Anthropic-compatible |\n| `oai` | OpenAI `/v1/chat/completions` | OpenAI, Azure OpenAI |\n| `oai-compatible` | OpenAI shape, custom `base_url` | vLLM, OpenRouter, Groq, etc. |\n\nAdd one from **Console → Model Cards**, or via CLI:\n\n```bash\noma models create \\\n  --model-id claude-prod \\\n  --provider ant \\\n  --model claude-sonnet-4-6 \\\n  --api-key sk-ant-...\noma models list\n```\n\nREST: `POST /v1/model_cards`, `GET /v1/model_cards`, `POST /v1/model_cards/:id` (rotate), `DELETE /v1/model_cards/:id`. Create runs a 6-second probe so a bad key fails loudly, not at first turn.\n\nKeys are AES-256-GCM-encrypted at rest under `PLATFORM_ROOT_SECRET` (label `model.cards.keys`); list responses surface only the last-4 preview. Rotate by POSTing a new `api_key` — no redeploy, no key versioning (re-run the backfill script if you rotate `PLATFORM_ROOT_SECRET` itself).\n\n---\n\n## Testing\n\n```bash\nnpm test          # unit + integration suite\nnpm run typecheck # zero errors\n```\n\n---\n\n## Documentation\n\nThe user-facing docs site lives at [`apps/docs`](apps/docs/) (Astro Starlight) and is published to **[docs.openma.dev](https://docs.openma.dev)**.\n\n```bash\npnpm dev:docs       # local preview at http://localhost:4321\npnpm build:docs     # static build into apps/docs/dist/\npnpm deploy:docs    # build + wrangler deploy (Cloudflare Worker static assets)\n```\n\nThe `docs/` folder at the repo root contains **internal design RFCs** — not the user-facing site.\n\n---\n\n## Contributing\n\n1. Fork the repository\n2. Create your feature branch (`git checkout -b feat/amazing-feature`)\n3. Run tests (`npm test \u0026\u0026 npm run typecheck`)\n4. Commit your changes\n5. Open a Pull Request\n\n---\n\n## License\n\n[Apache 2.0](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopenma-ai%2Fopen-managed-agents","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fopenma-ai%2Fopen-managed-agents","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopenma-ai%2Fopen-managed-agents/lists"}