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
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ OpenAgentCore is protocol-first and modular. Core orchestrates operations that p

OpenAgentCore is infrastructure. Change a boundary only when the existing protocol cannot express the behavior, and make that the smallest change that leaves the design intact. Hold the code to the standard of a careful, widely used open-source service.

Keep it concise. Write elegant code that reuses existing code and standard SDKs as far as possible, and avoid redundant code. Expose nothing that does not need to be exposed: no port, route, command or setting without a caller.

### Protocols at every boundary

- Each boundary between components has exactly one protocol: one code file (interface, wire types and validators) and one document. A protocol change edits both and every implementation in one change, reviewed on its own.
Expand Down
7 changes: 2 additions & 5 deletions deploy/compose/ports.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
# Host installation publishes Web and Core's loopback admin API for scripts on
# the host. Hosting platforms omit this file and route to web:8080 themselves.
# Host installation publishes Web. Hosting platforms omit this file and route
# to web:8080 themselves.
services:
web:
ports:
- "${OAC_HOST:-127.0.0.1}:${OAC_WEB_PORT:-8080}:8080"
core:
ports:
- "127.0.0.1:8091:8091"
4 changes: 2 additions & 2 deletions deploy/compose/test_compose.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,14 +83,14 @@ def test_public_url_can_be_configured_after_initial_startup(self):
{service: [item.get('target') for item in spec.get('volumes', [])]
for service, spec in self.compose['services'].items()})

def test_host_ports_publish_web_and_loopback_core(self):
def test_host_ports_publish_only_web(self):
env = dict(os.environ, OAC_DATA_DIR='/tmp/oac-compose-fixture', OAC_HOST='0.0.0.0')
hosted = json.loads(subprocess.check_output(
['docker', 'compose', '--env-file', os.devnull, '-f', str(self.compose_file),
'-f', str(ROOT / 'deploy/compose/ports.yaml'), 'config', '--format', 'json'], env=env))
published = {name: [(port.get('host_ip'), port['published']) for port in service.get('ports', [])]
for name, service in hosted['services'].items() if service.get('ports')}
self.assertEqual(published, {'web': [('0.0.0.0', '8080')], 'core': [('127.0.0.1', '8091')]})
self.assertEqual(published, {'web': [('0.0.0.0', '8080')]})

def test_platform_network_injection_keeps_the_file_valid(self):
# Dokploy isolated deployments attach a project network to every service.
Expand Down
5 changes: 1 addition & 4 deletions deploy/install.dev.sh
Original file line number Diff line number Diff line change
Expand Up @@ -103,11 +103,8 @@ pins = json.loads((root / "deploy/compose/smoke-pins.json").read_text())
"RELEASE_BASE": pins["release_base"],
"ARCHIVE_CHECKSUM": pins["archive_checksum"],
}))
# The release ports.yaml also publishes Core on 127.0.0.1:8091. A local trial
# reaches Core through Web, so only Web is published.
(dest / "ports.yaml").write_text(
"services:\n web:\n ports:\n - \"${OAC_HOST:-127.0.0.1}:${OAC_WEB_PORT:-8080}:8080\"\n")
PY
cp "$repo_root/deploy/compose/ports.yaml" "$install_dir/ports.yaml"

umask 077
cat >"$install_dir/.env" <<EOF
Expand Down
8 changes: 6 additions & 2 deletions deploy/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,12 @@ fi

cleanup() {
if [[ "$kept" != 1 && -d "$install_dir" ]]; then
(cd "$install_dir" && docker compose down --remove-orphans) >/dev/null 2>&1 || true
(
cd "$install_dir"
docker compose down --remove-orphans
# Containers own data/; remove it from a container as well.
if [[ -d data ]]; then docker compose run --rm --no-deps --entrypoint find init /data -mindepth 1 -delete; fi
) >/dev/null 2>&1 || true
rm -rf "$install_dir"
fi
}
Expand Down Expand Up @@ -104,7 +109,6 @@ umask 077
docker compose pull
docker compose create core
docker compose cp core:/usr/local/bin/oac ./oac
chmod 755 ./oac
docker compose up -d --wait
)
kept=1
Expand Down
2 changes: 1 addition & 1 deletion docs/api/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Core serves three namespaces. Each has one kind of caller and its own credential

A credential used in another namespace gets 401: a Project API key on `/core/v1` or `/api/v1`, the Core key on `/v1` or `/api/v1`. How Projects and keys behave is in [Projects own assets](../concepts.md#projects-own-assets).

**Routing.** Web forwards `/v1`, `/api/v1` and `/docs` to Core unchanged ([console server](../web/console-server.md)). A signed-in browser reaches `/core/v1` through Web, which adds the Core key. Operator scripts call `/core/v1` on `127.0.0.1:8091` ([script the Core API](../getting-started/operations.md#script-the-core-api)).
**Routing.** Web forwards `/v1`, `/api/v1` and `/docs` to Core unchanged ([console server](../web/console-server.md)). A signed-in browser reaches `/core/v1` through Web, which adds the Core key. Operator scripts call `/core/v1` inside Core's network namespace on the Core host ([script the Core API](../getting-started/operations.md#script-the-core-api)).

**API reference.** Core serves a read-only Swagger UI of the three namespaces at `/docs`, and the documents at `/docs/openapi.yaml`, `/docs/core.openapi.yaml` and `/docs/runtime.openapi.yaml`. No credential is required, and the page sends no API requests. Open it on the console origin, for example `http://localhost:8080/docs`. The browser loads Swagger UI from `unpkg.com`.

Expand Down
4 changes: 2 additions & 2 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ To change it, point the reverse proxy at the new address first, then edit `OAC_P
| `OAC_PUBLIC_URL` | `http://localhost:8080` | Origin applications, nodes, sandboxes and self-hosted executors use. Managed domain setup writes the HTTPS origin and recreates Core and Web |
| `OAC_HOST` | `127.0.0.1` | Address published by `ports.yaml`. `install.sh` sets `0.0.0.0` |
| `OAC_WEB_PORT` | `8080` | Host port of Web |
| `COMPOSE_FILE` | `compose.yaml:ports.yaml` | The Compose files. `ports.yaml` publishes Web and Core's loopback admin API; hosting platforms omit it |
| `COMPOSE_FILE` | `compose.yaml:ports.yaml` | The Compose files. `ports.yaml` publishes Web; hosting platforms omit it |
| `OAC_LOG_LEVEL` | `info` | `debug`, `info`, `warn` or `error` |
| `OAC_LOG_FORMAT` | `auto` | `auto`, `text` or `json` |
| `OAC_LOG_ADD_SOURCE` | unset | `1` adds source locations |
Expand Down Expand Up @@ -135,7 +135,7 @@ The installer creates the installation directory, `~/.oac/core` by default, with
| `data/state/` | Private Provider state, including E2B receipts | Core |
| `.oac.lock` | The installation lock | Mutating `oac` commands |

The Compose project is named `oac-<10 hex digits>`. Its services are `init`, `database`, `core` and `web`. Core applies database migrations when it starts. `web` serves the console and forwards `/v1` and `/api/v1` to Core, and it is the only service that publishes `OAC_WEB_PORT`. Host installs also publish Core's admin API on `127.0.0.1:8091`. No service receives a Docker socket. Apart from Docker's storage, nothing is written outside the installation directory.
The Compose project is named `oac-<10 hex digits>`. Its services are `init`, `database`, `core` and `web`. Core applies database migrations when it starts. `web` serves the console and forwards `/v1` and `/api/v1` to Core, and it is the only service with a published port, `OAC_WEB_PORT`. No service receives a Docker socket. Apart from Docker's storage, nothing is written outside the installation directory.

## Appendix: Core environment without the installer

Expand Down
2 changes: 1 addition & 1 deletion docs/getting-started/install-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ The installer saves no sandbox backend. After signing in, open **System** → **

## Listeners and access

The default installation publishes Web on `--web-port` (8080) at `--host 0.0.0.0`. Core's admin API stays on `127.0.0.1:8091`. PostgreSQL stays private. `--host` is an IPv4 or IPv6 address, without a port, scheme or zone. Use a concrete server IP in the browser, not a wildcard.
The default installation publishes Web on `--web-port` (8080) at `--host 0.0.0.0`. Core and PostgreSQL stay private. `--host` is an IPv4 or IPv6 address, without a port, scheme or zone. Use a concrete server IP in the browser, not a wildcard.

`--public-url` sets `OAC_PUBLIC_URL`, the origin applications, nodes and executors use. Set it to the HTTPS origin your reverse proxy serves.

Expand Down
4 changes: 2 additions & 2 deletions docs/getting-started/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ This page follows the default path. Every flag, existing reverse proxies and off
- Linux amd64 and curl.
- Docker Engine with Docker Compose 2.26.0 or newer (`docker compose version`).
- An account that can run `docker` and write to its home directory. Ordinary users and root both work; the installer never calls sudo.
- Free port 8080 for Web. Core's admin API uses `127.0.0.1:8091`. See [ports](./install-options.md#ports). Docker must be able to publish them; the installer does not change host policy.
- Free port 8080 for Web. See [ports](./install-options.md#ports). Docker must be able to publish it; the installer does not change host policy.
- For anything off this machine, the origin in `OAC_PUBLIC_URL` must be the address browsers, nodes and executors use. You can sign in on this machine first.

The Core host needs no KVM; nodes that run microsandbox do.
Expand All @@ -40,7 +40,7 @@ The script downloads that release's Compose files, checks their SHA-256, and:

1. checks Linux amd64, Docker Compose 2.26 or newer, and that the ports it will publish are free;
2. creates the [installation directory](../configuration.md#installation-directory), `~/.oac/core`, writes `.env`, and copies the `oac` command out of the Core image;
3. starts the services with Docker Compose. Web serves the console on port 8080 and forwards `/v1`, `/api/v1` and `/docs` to Core. Core's admin API stays on `127.0.0.1:8091`. PostgreSQL is not published.
3. starts the services with Docker Compose. Web serves the console on port 8080 and forwards `/v1`, `/api/v1` and `/docs` to Core. Core and PostgreSQL are not published.

It saves no sandbox backend, adds no node, creates no Project or key and makes no model request. It ends by printing the console address and how to read the Core key.

Expand Down
26 changes: 15 additions & 11 deletions docs/getting-started/operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ docker compose -f ~/.oac/core/compose.yaml ps
| `oac apply` | Runs `oac-core check-config`, then `docker compose up -d --wait`. A failed check changes no service |
| `oac core-key [--show]` | Prints the Core key path, or the key itself with `--show` |
| `oac rotate-core-key` | Replaces the Core key and restarts Core and Web |
| `docker compose down --rmi all` | Removes the containers and images. Delete the installation directory afterwards |
| `docker compose down` | Removes the containers. Data is kept; to delete it, [uninstall](#uninstall) |

For a second installation, use its directory, such as `~/.oac/second`.

Expand Down Expand Up @@ -70,14 +70,15 @@ Keep it private. Web reads `data/secrets/web/core.key`. Core reads only its SHA-

### Script the Core API

Run scripts on the Core host against Core's loopback port. This helper reads the key from its file, keeping it off the command line:
Core publishes no host port. On the Core host, this helper runs `curl` in Core's network namespace and passes the key on stdin, keeping it off the command line:

```sh
core() { # core METHOD PATH [JSON body]
curl -fsS -X "$1" "http://127.0.0.1:8091/core/v1$2" \
-H @<(printf 'Authorization: Bearer %s\n' "$(~/.oac/core/oac core-key --show)") \
-H 'Content-Type: application/json' ${3:+-d "$3"}
}
core() ( # core METHOD PATH [JSON body]
cd ~/.oac/core
./oac core-key --show | sed 's/^/Authorization: Bearer /' |
docker run -i --rm --network "container:$(docker compose ps -q core)" curlimages/curl \
-fsS -X "$1" "http://127.0.0.1:8091/core/v1$2" -H @- -H 'Content-Type: application/json' ${3:+-d "$3"}
)
```

| Task | Command |
Expand Down Expand Up @@ -136,11 +137,14 @@ Never prune Docker volumes or delete native harness history to make a retry pass
## Uninstall

```sh
docker compose -f ~/.oac/core/compose.yaml down --rmi all --remove-orphans
rm -rf ~/.oac/core
cd ~/.oac/core
docker compose down --remove-orphans
docker compose run --rm --no-deps --entrypoint find init /data -mindepth 1 -delete
docker compose down --rmi all
cd && rm -rf ~/.oac/core
```

`down` removes the containers and images. `rm` removes the installation directory. Do the first only when you mean to delete the data.
The containers own `data/`, so the `init` image deletes its contents; then `down --rmi all` removes the images and `rm` removes the installation directory. Run these only when you mean to delete the data.

All data goes with it: Projects and API keys, Session history, stored credentials and the Core key. To keep the data, stop the installation with `docker compose stop` instead, or [back it up](#back-up) first.

Expand Down Expand Up @@ -183,7 +187,7 @@ Mutating `oac` commands hold `.oac.lock`. If another command holds it, retry aft
| Listener | Host installation | Behind a reverse proxy |
| --- | --- | --- |
| Web and the API | Web publishes `OAC_WEB_PORT` (8080) on `OAC_HOST` | Web publishes `OAC_WEB_PORT` on `OAC_HOST`. Your proxy should use `127.0.0.1` |
| Core admin API | `127.0.0.1:8091`. Web forwards `/v1`, `/api/v1` and `/docs` | `127.0.0.1:8091`. Web forwards `/v1`, `/api/v1` and `/docs` |
| Core | No published port. Web forwards `/v1`, `/api/v1` and `/docs` | No published port. Web forwards `/v1`, `/api/v1` and `/docs` |
| PostgreSQL | No published port | No published port |

Web signs administrators in with the Core key, checks the origin of every request, and forwards signed-in `/core/v1` requests to Core with the Core key, which stays on the server. It forwards `/v1` and `/api/v1` to Core unchanged, with the caller's own credential, serves only the non-secret node payload at `/node-install/`, and has no Docker or KVM access. Machine routes under `/api/v1` use their own enrollment and connection credentials. No service receives a Docker socket.
Expand Down
4 changes: 2 additions & 2 deletions docs/zh/api/index.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "API 命名空间和凭据"
source: docs/api/index.md
source_hash: 922ad1d9b3ea7549aa6b845847f8e0278a637a1b986d41b264b9cf892429a8df
source_hash: 11df05084e85f1bc05650e11c2c744318b92ba7ac90280207956fce712335122
---

Core 提供三个命名空间。每个命名空间都有一种调用方及其独立凭据,凭据只能在其所属命名空间中使用。
Expand All @@ -14,7 +14,7 @@ Core 提供三个命名空间。每个命名空间都有一种调用方及其独

在其他命名空间中使用凭据会返回 401:在 `/core/v1` 或 `/api/v1` 上使用 Project API key,或者在 `/v1` 或 `/api/v1` 上使用 Core key。有关 Project 和密钥的行为,请参阅 [Project 自有资产](../concepts.md#projects-own-assets)。

**路由。** Web 把 `/v1`、`/api/v1` 和 `/docs` 原样转发到 Core([控制台服务器](../web/console-server.md))。已登录的浏览器通过 Web 访问 `/core/v1`,由 Web 附上 Core key。操作员脚本在 `127.0.0.1:8091` 调用 `/core/v1`([编写 Core API 脚本](../getting-started/operations.md#script-the-core-api))。
**路由。** Web 把 `/v1`、`/api/v1` 和 `/docs` 原样转发到 Core([控制台服务器](../web/console-server.md))。已登录的浏览器通过 Web 访问 `/core/v1`,由 Web 附上 Core key。操作员脚本在 Core 主机上从 Core 的网络命名空间内调用 `/core/v1`([编写 Core API 脚本](../getting-started/operations.md#script-the-core-api))。

**API 参考。** Core 在 `/docs` 提供三个命名空间的只读 Swagger UI,文档位于 `/docs/openapi.yaml`、`/docs/core.openapi.yaml` 和 `/docs/runtime.openapi.yaml`。不需要凭据,页面也不发送 API 请求。在控制台源地址打开,例如 `http://localhost:8080/docs`。浏览器从 `unpkg.com` 加载 Swagger UI。

Expand Down
6 changes: 3 additions & 3 deletions docs/zh/configuration.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "配置参考"
source: docs/configuration.md
source_hash: 610b3a85b453d00b575f9fb4d42c87ec89dc3803bb238fd4c36ef734aba6c7e2
source_hash: 62ad7f6c64329e0d3e9bda9b4936f5c13281eb18a1011b057b2f6288ddad35cc
---

Core 安装的每项设置都恰好只有一个归属位置。共有两类:
Expand Down Expand Up @@ -50,7 +50,7 @@ Web 的 **System** 页面显示该安装的地址、默认模型和沙箱配置
| `OAC_PUBLIC_URL` | `http://localhost:8080` | Origin applications, nodes, sandboxes and self-hosted executors use. Managed domain setup writes the HTTPS origin and recreates Core and Web |
| `OAC_HOST` | `127.0.0.1` | Address published by `ports.yaml`. `install.sh` sets `0.0.0.0` |
| `OAC_WEB_PORT` | `8080` | Host port of Web |
| `COMPOSE_FILE` | `compose.yaml:ports.yaml` | The Compose files. `ports.yaml` publishes Web and Core's loopback admin API; hosting platforms omit it |
| `COMPOSE_FILE` | `compose.yaml:ports.yaml` | Compose 文件。`ports.yaml` 发布 Web;托管平台省略它 |
| `OAC_LOG_LEVEL` | `info` | `debug`, `info`, `warn` or `error` |
| `OAC_LOG_FORMAT` | `auto` | `auto`, `text` or `json` |
| `OAC_LOG_ADD_SOURCE` | unset | `1` adds source locations |
Expand Down Expand Up @@ -139,7 +139,7 @@ Web 的 **System** 页面显示该安装的地址、默认模型和沙箱配置
| `data/state/` | 私有 Provider 状态,包括 E2B 回执 | Core |
| `.oac.lock` | 安装锁 | 会修改安装状态的 `oac` 命令 |

Compose 项目名为 `oac-<10 hex digits>`。服务包括 `init`、`database`、`core` 和 `web`。Core 启动时执行数据库迁移。`web` 提供控制台并把 `/v1` 和 `/api/v1` 转发到 Core,是唯一发布 `OAC_WEB_PORT` 的服务。主机安装还把 Core 的管理 API 发布在 `127.0.0.1:8091`。没有服务持有 Docker 套接字。除 Docker 存储外,不会向安装目录之外写入任何内容。
Compose 项目名为 `oac-<10 hex digits>`。服务包括 `init`、`database`、`core` 和 `web`。Core 启动时执行数据库迁移。`web` 提供控制台并把 `/v1` 和 `/api/v1` 转发到 Core,是唯一发布端口(`OAC_WEB_PORT`)的服务。没有服务持有 Docker 套接字。除 Docker 存储外,不会向安装目录之外写入任何内容。

## 附录:没有安装程序时的 Core 环境 {#appendix-core-environment-without-the-installer}

Expand Down
10 changes: 2 additions & 8 deletions docs/zh/getting-started/install-options.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "安装选项与高级部署"
source: docs/getting-started/install-options.md
source_hash: f9a5aae96ce8789030eda5cb399eac7c2e6bc6bd4d9bcce18138373c97cb0e96
source_hash: be5e1a127f654e75611c7b670fba9fc10cb3b78165590a7f6616eb1b79dd2f14
---

[默认安装](install.md)无需任何选项。使用本页可以在现有反向代理后运行,或者在无法访问互联网时进行安装。
Expand Down Expand Up @@ -74,7 +74,7 @@ docker compose -f compose.yaml exec web oac-web core-key

## 监听器与访问 {#listeners-and-access}

默认安装在 `--host 0.0.0.0` 的 `--web-port`(8080)上发布 Web。Core 的管理 API 留在 `127.0.0.1:8091`。PostgreSQL 保持私有。`--host` 是不含端口、协议或区域的 IPv4 或 IPv6 地址。请在浏览器中使用服务器的具体 IP,而不是通配地址。
默认安装在 `--host 0.0.0.0` 的 `--web-port`(8080)上发布 Web。Core 和 PostgreSQL 保持私有。`--host` 是不含端口、协议或区域的 IPv4 或 IPv6 地址。请在浏览器中使用服务器的具体 IP,而不是通配地址。

`--public-url` 设置 `OAC_PUBLIC_URL`,即应用、节点和执行器使用的源地址。把它设为反向代理提供的 HTTPS 源地址。

Expand All @@ -91,12 +91,6 @@ docker compose -f compose.yaml exec web oac-web core-key

请用 `--host 127.0.0.1` 安装,并把反向代理指向 Web,默认是 `127.0.0.1:8080`。Web 把 `/v1`、`/api/v1` 和 `/docs` 转到 Core,其余由自己提供。

| 路径 | 目标 | 调用方 |
| --- | --- | --- |
| `/v1`、`/v1/*` | Core,默认为 `127.0.0.1:8091` | 应用程序,使用 Project API 密钥 |
| `/api/v1/*` | Core,`127.0.0.1:8091` | 节点、沙箱和自托管机器。使用 WebSockets |
| 其他所有路径 | Web,默认为 `127.0.0.1:8080` | 浏览器,以及通过 `/node-install/*` 访问的节点安装程序 |

反向代理必须:

- **保留 Host。** Web 仅接受 `OAC_PUBLIC_URL` 中的主机。
Expand Down
Loading
Loading