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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
20 changes: 14 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ permissions:
jobs:
test:
runs-on: ubuntu-latest
env:
GOWORK: "off"
GOFLAGS: "-mod=readonly"
steps:
- name: Check out repository
uses: actions/checkout@v4
Expand All @@ -22,17 +25,22 @@ jobs:
go-version-file: go.mod
cache: true

- name: Verify vendored dependencies
run: go list -mod=vendor ./...
- name: Download dependencies
run: go mod download

- name: Verify dependency graph
run: |
go mod verify
go list ./...

- name: Test
run: go test -mod=vendor ./...
run: go test ./...

- name: Vet
run: GOWORK=off go vet -mod=vendor ./...
run: go vet ./...

- name: Race test
run: GOWORK=off go test -race -mod=vendor ./...
run: go test -race ./...

- name: Build
run: go build -mod=vendor ./cmd/openlinker-agent-node
run: go build ./cmd/openlinker-agent-node
9 changes: 9 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ jobs:
package-binaries:
name: Package ${{ matrix.goos }}/${{ matrix.goarch }}
runs-on: ubuntu-24.04
env:
GOWORK: "off"
GOFLAGS: "-mod=readonly"
permissions:
contents: read
strategy:
Expand Down Expand Up @@ -41,6 +44,12 @@ jobs:
go-version-file: go.mod
cache: true

- name: Verify dependency graph
run: |
go mod download
go mod verify
go list ./...

- name: Package binary
id: package
shell: bash
Expand Down
21 changes: 11 additions & 10 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,24 @@ runtime protocol, adapter interfaces, and CLI behavior are declared stable.
- The pinned `openlinker-go` SDK now owns the complete reliable Runtime Worker.
Public Go names are generation-free `Runtime*`, and Runtime URLs do not carry
a protocol generation.
- Normal startup uses `OPENLINKER_URL` to discover the dedicated mTLS Runtime
origin from `/.well-known/openlinker.json`. `OPENLINKER_RUNTIME_URL` remains
an advanced HTTPS override.
- Normal startup uses `OPENLINKER_URL` to discover the Runtime origin and its
token-only or mTLS policy from `/.well-known/openlinker.json`.
`OPENLINKER_RUNTIME_URL` remains an advanced address override but no longer
bypasses discovery or downgrades its security policy.
- Delegated Agent calls require an explicit idempotency key. Reuse a key only
for retries of the same intent.
- The optional public A2A listener no longer executes an Adapter or owns
in-memory task and push-notification state. It retains only local Agent Card,
bearer-authentication, request-size, and timeout guards; the Go SDK proxies
every stateful A2A operation to Core over Agent Token plus mTLS. REST and
every stateful A2A operation to Core over Agent Token plus the discovered security policy. REST and
JSON-RPC Agent Card responses remain stateless and local so their external
URL continues to identify the AgentNode listener.

### SDK boundary

- Pinned and vendored `openlinker-go` commit
`4ce0eec90a533e235cb28e2045f136529186bf51`.
- The SDK owns discovery, TLS 1.3 mTLS, Session identity, WebSocket/Pull
- Pinned `openlinker-go` commit
`bcb5823c5bd08da78802691a31a05501db74e7d8`; dependencies now use standard Go module resolution instead of a checked-in vendor tree.
- The SDK owns discovery, token-only/TLS 1.3 mTLS policy, Session identity, WebSocket/Pull
switching, assignment confirmation, lease renewal, resume, cancellation,
drain, durable assignment state, encrypted Event/Result delivery, ACK repair,
backpressure, and duplicate-execution prevention.
Expand All @@ -53,8 +54,8 @@ runtime protocol, adapter interfaces, and CLI behavior are declared stable.
integration.
- Added regressions for Stop during Worker startup and for Adapter error,
cancellation, and panic mapping.
- Added mTLS proxy regressions for all public A2A operation families, SSE,
- Added token-only and mTLS proxy regressions for public A2A operation families, SSE,
identity/header isolation, oversized requests, unavailable Core, and
listener generation-safe shutdown/restart.
- Verified both repositories with ordinary and race-enabled Go tests, and
verified Agent Node from the vendored dependency graph.
- Verified both repositories with ordinary and race-enabled Go tests and a
read-only standard module dependency graph.
36 changes: 20 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ instead.
## Technical boundary

Agent Node does not implement a Runtime client or state machine. The pinned Go
SDK owns discovery, mTLS, Session identity, WebSocket/Pull switching,
SDK owns discovery and the token-only or mTLS security policy, Session identity, WebSocket/Pull switching,
assignment confirmation, lease renewal, resume, cancellation, drain, the
encrypted journal, and stable Event/Result replay. The same SDK behavior is
available directly to any Go application through `NewRuntimeWorker`.
Expand Down Expand Up @@ -90,14 +90,16 @@ go test ./...
go build ./cmd/openlinker-agent-node
```

On first start the SDK creates the Node ID and P-256 private key inside the
private data directory, binds that public key one-to-one to the Agent Token,
and obtains a 24-hour client certificate. Renewal is automatic.
Platform discovery decides whether the Runtime is token-only or requires mTLS.
For mTLS, the SDK can create the private key inside the data directory and
automatically enroll and renew a short-lived client certificate.

Run a local HTTP backend:

```bash
OPENLINKER_URL=https://openlinker.example \
OPENLINKER_NODE_ID=11111111-1111-4111-8111-111111111111 \
OPENLINKER_AGENT_ID=22222222-2222-4222-8222-222222222222 \
OPENLINKER_AGENT_TOKEN=ol_agent_xxx \
OPENLINKER_AGENT_NODE_DATA_DIR=/var/lib/openlinker-agent-node \
OPENLINKER_AGENT_NODE_TRANSPORT=auto \
Expand All @@ -123,26 +125,28 @@ never expired or deleted.
At startup, the Go SDK Worker reads the public connection manifest from
`$OPENLINKER_URL/.well-known/openlinker.json` and discovers the dedicated
Runtime origin. Discovery uses a separate five-second HTTP client, follows no
redirects, reads at most 64 KiB, and sends neither the Agent Token nor the mTLS
client certificate. A missing, disabled, insecure, or malformed Runtime entry
redirects, reads at most 64 KiB, and sends neither the Agent Token nor a client
certificate. The same manifest selects token-only or mTLS security. A missing, disabled, insecure, or malformed Runtime entry
stops startup instead of falling back to the ordinary API origin.

| Variable | Purpose |
| --- | --- |
| `OPENLINKER_URL` | OpenLinker platform origin used to discover the Runtime connection |
| `OPENLINKER_NODE_ID` | Optional legacy Node UUID override; generated automatically by default |
| `OPENLINKER_AGENT_ID` | Optional legacy Agent UUID override; resolved from the Agent Token by default |
| `OPENLINKER_NODE_ID` | Stable Runtime Node UUID; required for token-only Runtime |
| `OPENLINKER_AGENT_ID` | Agent UUID; required for token-only Runtime |
| `OPENLINKER_AGENT_TOKEN` | Long-lived Agent Token kept inside the node |
| `OPENLINKER_AGENT_NODE_DATA_DIR` | Directory selected for the SDK `FileRuntimeStore` |
| `OPENLINKER_AGENT_NODE_MTLS_CERT_FILE` | Optional external-PKI compatibility certificate |
| `OPENLINKER_AGENT_NODE_MTLS_KEY_FILE` | Optional external-PKI compatibility private key |
| `OPENLINKER_AGENT_NODE_MTLS_CA_FILE` | Optional external-PKI compatibility CA bundle |
| `OPENLINKER_AGENT_NODE_MTLS_CERT_FILE` | Optional external-PKI certificate used only when discovery requires mTLS |
| `OPENLINKER_AGENT_NODE_MTLS_KEY_FILE` | Optional external-PKI private key; configure the complete cert/key/CA group |
| `OPENLINKER_AGENT_NODE_MTLS_CA_FILE` | Optional external-PKI CA bundle |
| `OPENLINKER_AGENT_NODE_MTLS_SERVER_NAME` | Optional certificate server-name override |
| `OPENLINKER_AGENT_NODE_TRANSPORT` | `auto` (default), `ws`, or `pull`; all share one Runtime session |

`OPENLINKER_RUNTIME_URL` is an advanced override for integration tests and
private-network routing. It must be an absolute HTTPS origin and skips public
discovery. Normal deployments should leave it unset.
`OPENLINKER_RUNTIME_URL` is an advanced connection-address override for tests
and private routing. Platform discovery still runs and remains authoritative
for the security policy; an override cannot downgrade an mTLS manifest. Normal
deployments should leave it unset. A Runtime URL without `OPENLINKER_URL` uses
the legacy direct mode and requires a complete mTLS configuration.

Useful tuning options are `OPENLINKER_AGENT_NODE_CAPACITY`,
`OPENLINKER_AGENT_NODE_CLAIM_WAIT_SECONDS`,
Expand Down Expand Up @@ -267,7 +271,7 @@ call whose `CallAgentOptions.IdempotencyKey` is empty.
Agent Node can expose a local inbound A2A URL for compatibility. The listener
serves the local Agent Card and validates `OPENLINKER_PUBLIC_A2A_TOKEN`; the Go
SDK forwards every message, task, push-notification, stateful JSON-RPC, and SSE
request to Core over the configured Agent Token and mTLS identity. REST and
request to Core over the configured Agent Token and discovered security policy. REST and
JSON-RPC Agent Card responses remain local so their external URL continues to
name this listener. Core remains the sole authority for A2A task, run, stream,
and push state. The listener is disabled by default:
Expand All @@ -287,7 +291,7 @@ their lifetime follows client cancellation and the Core stream.

## Security and operations

- Treat the Agent Token, mTLS private key, SDK-managed spool key, assignment
- Treat the Agent Token, any mTLS private key, SDK-managed spool key, assignment
payloads, and helper tokens as secrets.
- Do not mount the runtime data directory into backend containers.
- Keep command and Codex workspaces isolated and narrowly permissioned.
Expand Down
27 changes: 15 additions & 12 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Agent 和远程 MCP 服务也不需要它。

## 技术边界

Agent Node 不重复实现 Runtime client 或状态机。固定版本的 Go SDK 负责地址发现、mTLS、
Agent Node 不重复实现 Runtime client 或状态机。固定版本的 Go SDK 负责地址和 token-only/mTLS 安全策略发现
连接身份、WebSocket/Pull 切换、任务确认、续租、恢复、取消、停止接收新任务、加密任务记录,
以及事件和结果的可靠重传。Go 应用也可以直接通过 `NewRuntimeWorker` 使用同一套能力。

Expand Down Expand Up @@ -78,13 +78,15 @@ go test ./...
go build ./cmd/openlinker-agent-node
```

首次启动时,SDK 会在私有目录生成 Node ID 和 P-256 私钥,将公钥一对一绑定到 Agent Token
并取得 24 小时客户端证书;续期全自动完成
平台发现决定 Runtime 使用 token-only 还是 mTLS。mTLS 策略下,SDK 可在私有目录生成私钥
并自动登记和续期短期客户端证书

运行本地 HTTP backend:

```bash
OPENLINKER_URL=https://openlinker.example \
OPENLINKER_NODE_ID=11111111-1111-4111-8111-111111111111 \
OPENLINKER_AGENT_ID=22222222-2222-4222-8222-222222222222 \
OPENLINKER_AGENT_TOKEN=ol_agent_xxx \
OPENLINKER_AGENT_NODE_DATA_DIR=/var/lib/openlinker-agent-node \
OPENLINKER_AGENT_NODE_TRANSPORT=auto \
Expand All @@ -107,24 +109,25 @@ SDK 加密 spool 的上限是 512 MiB 和 10,000 条记录。使用量达到 80%
启动时,Go SDK Worker 会读取
`$OPENLINKER_URL/.well-known/openlinker.json`,自动发现专用 Runtime 地址。发现请求
使用独立的 5 秒 HTTP client,不跟随跳转,最多读取 64 KiB,也不会携带 Agent Token
mTLS client certificate。Runtime 信息缺失、关闭、不安全或格式错误时,节点会直接
或 client certificate;同一份清单决定 token-only/mTLS 策略。Runtime 信息缺失、关闭、不安全或格式错误时,节点会直接
停止启动,不会退回普通 API 地址。

| 环境变量 | 用途 |
| --- | --- |
| `OPENLINKER_URL` | OpenLinker 平台地址,用于自动发现 Runtime 连接信息 |
| `OPENLINKER_NODE_ID` | 可选的旧版 Node UUID 覆盖;默认自动生成 |
| `OPENLINKER_AGENT_ID` | 可选的旧版 Agent UUID 覆盖;默认从 Agent Token 解析 |
| `OPENLINKER_NODE_ID` | 稳定的 Runtime Node UUID;token-only Runtime 必填 |
| `OPENLINKER_AGENT_ID` | Agent UUID;token-only Runtime 必填 |
| `OPENLINKER_AGENT_TOKEN` | 只保留在节点内的长效 Agent Token |
| `OPENLINKER_AGENT_NODE_DATA_DIR` | 交给 SDK `FileRuntimeStore` 的目录 |
| `OPENLINKER_AGENT_NODE_MTLS_CERT_FILE` | 可选的外部 PKI 兼容证书 |
| `OPENLINKER_AGENT_NODE_MTLS_KEY_FILE` | 可选的外部 PKI 兼容私钥 |
| `OPENLINKER_AGENT_NODE_MTLS_CERT_FILE` | 仅发现要求 mTLS 时可选的外部 PKI 证书 |
| `OPENLINKER_AGENT_NODE_MTLS_KEY_FILE` | 可选外部 PKI 私钥,必须完整配置 cert/key/CA 组 |
| `OPENLINKER_AGENT_NODE_MTLS_CA_FILE` | 可选的外部 PKI 兼容 CA bundle |
| `OPENLINKER_AGENT_NODE_MTLS_SERVER_NAME` | 可选的证书 server name 覆盖值 |
| `OPENLINKER_AGENT_NODE_TRANSPORT` | `auto`(默认)、`ws` 或 `pull`;三者共用同一 Runtime session |

`OPENLINKER_RUNTIME_URL` 是集成测试和特殊私网路由使用的高级覆盖项。它必须是绝对
HTTPS origin;设置后会跳过公开发现。普通部署无需填写。
`OPENLINKER_RUNTIME_URL` 是集成测试和特殊私网路由使用的高级连接地址覆盖项。平台发现
仍会执行并决定安全策略,覆盖值不能把 mTLS 清单降级为 token-only。普通部署无需填写。
没有 `OPENLINKER_URL` 的旧式 Runtime URL 直连仍要求完整 mTLS 配置。

可调参数包括 `OPENLINKER_AGENT_NODE_CAPACITY`、
`OPENLINKER_AGENT_NODE_CLAIM_WAIT_SECONDS`、
Expand Down Expand Up @@ -243,7 +246,7 @@ curl -X POST "$OPENLINKER_AGENT_NODE_HELPER_EVENTS_URL" \

Agent Node 可以提供一个兼容旧接入方式的本地 A2A 地址。监听器只负责展示本地
Agent Card 并校验 `OPENLINKER_PUBLIC_A2A_TOKEN`;所有 message、task、push、
有状态 JSON-RPC 与 SSE 请求都由 Go SDK 通过 Agent Token 和 mTLS 身份转发到 Core。
有状态 JSON-RPC 与 SSE 请求都由 Go SDK 通过 Agent Token 和发现得到的安全策略转发到 Core。
REST 和 JSON-RPC Agent Card 响应仍在本地生成,以保持外部 URL 指向此监听器。
A2A task、run、stream 与 push 状态的唯一权威仍是 Core。该监听器默认关闭:

Expand All @@ -261,7 +264,7 @@ OPENLINKER_PUBLIC_A2A_TOKEN=optional-bearer-token

## 安全与运维

- Agent Token、mTLS private key、SDK 管理的 spool key、assignment payload 和 helper token 都是密钥。
- Agent Token、可能存在的 mTLS private key、SDK 管理的 spool key、assignment payload 和 helper token 都是密钥。
- 不要把 runtime 数据目录挂载进 backend container。
- command 与 Codex workspace 应隔离,并只授予必要权限。
- 优雅关闭会先上报 capacity 为 0,等待 active adapter,关闭 runtime session,再释放数据目录锁。
Expand Down
4 changes: 2 additions & 2 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ release line.

## Security-Sensitive Areas

- wiring Agent Token, mTLS paths, and the SDK file-store directory without
- wiring Agent Token, optional external-PKI paths, and the SDK file-store directory without
exposing them to Adapters
- passing assignment-scoped capabilities only through SDK-owned calls
- localhost helper token scope
Expand All @@ -28,7 +28,7 @@ release line.
- public A2A server request handling
- workspace isolation for the Codex adapter

The pinned `openlinker-go` SDK owns mTLS loading, Runtime transport and Session
The pinned `openlinker-go` SDK owns discovered token-only/mTLS security, Runtime transport and Session
state, encrypted journal/spool handling, lease fencing, resume, cancellation,
and duplicate-execution prevention. Report defects that reproduce in the SDK
to that repository; report leaks or unsafe wiring introduced by this host here.
Expand Down
4 changes: 2 additions & 2 deletions SECURITY.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ release tag。除非维护者明确公告,否则旧 commit 不承诺 backport

## 敏感区域

- Agent Token、mTLS 路径与 SDK 文件存储目录的接线,且不能暴露给 Adapter
- Agent Token、可选外部 PKI 路径与 SDK 文件存储目录的接线,且不能暴露给 Adapter
- assignment-scoped capability 只能通过 SDK 持有的调用路径使用
- localhost helper token 作用域
- adapter 命令执行和环境变量处理
- active run 中的 A2A delegation
- public A2A server 请求处理
- Codex adapter 的 workspace 隔离

固定版本的 `openlinker-go` SDK 负责 mTLS 加载、Runtime transport 与 Session 状态、
固定版本的 `openlinker-go` SDK 负责发现得到的 token-only/mTLS 安全策略、Runtime transport 与 Session 状态、
加密 journal/spool、lease fencing、resume、取消和重复执行防护。能直接在 SDK 复现的
问题请提交到 Go SDK 仓库;由本宿主接线造成的泄露或不安全行为仍在这里报告。

Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module github.com/OpenLinker-ai/openlinker-agent-node

go 1.25.0

require github.com/OpenLinker-ai/openlinker-go v0.2.0-rc1.0.20260716110201-4ce0eec90a53
require github.com/OpenLinker-ai/openlinker-go v0.2.0-rc1.0.20260721114051-7ebd3239780d

require (
github.com/gorilla/websocket v1.5.3 // indirect
Expand All @@ -11,6 +11,6 @@ require (
golang.org/x/text v0.37.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect
google.golang.org/grpc v1.82.0 // indirect
google.golang.org/grpc v1.82.1 // indirect
google.golang.org/protobuf v1.36.11 // indirect
)
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
github.com/OpenLinker-ai/openlinker-go v0.2.0-rc1.0.20260716110201-4ce0eec90a53 h1:h4Y6xqZ9lP7NqZjWqvJ2MAnZEiZXd9INJUk29YKms1E=
github.com/OpenLinker-ai/openlinker-go v0.2.0-rc1.0.20260716110201-4ce0eec90a53/go.mod h1:hZI+rinQqwQC5NH+RVIfbA/nmn5UWAOYMRA6cyOiKT4=
github.com/OpenLinker-ai/openlinker-go v0.2.0-rc1.0.20260721114051-7ebd3239780d h1:ecve+UkBh+mvtGxraNM1/vZ/sNXP14GJlfH9Q8gorq0=
github.com/OpenLinker-ai/openlinker-go v0.2.0-rc1.0.20260721114051-7ebd3239780d/go.mod h1:bgtqOKPzha8l1oUpFEJQTj563rwUf/rcNmsJEyhbl6g=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
Expand Down Expand Up @@ -38,7 +38,7 @@ google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 h1:
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478/go.mod h1:C6ADNqOxbgdUUeRTU+LCHDPB9ttAMCTff6auwCVa4uc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.82.0 h1:vguDnZUPjE26w09A63VoxZPnvPjB5Riyc0mkXPFmAIU=
google.golang.org/grpc v1.82.0/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE=
google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
Loading
Loading