diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9d0b73d19..8041a1c15 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,7 +20,7 @@ Every subject has one owner. Most owners are the page whose title names the subj | E2B and microsandbox Provider helper implementation | [E2B helper](services/core/tools/e2b-provider/README.md), [microsandbox helper](services/core/tools/microsandbox-provider/README.md) | | Distribution builds, Runtime image builds, CI and publication | [Build and release OpenAgentCore](docs/maintainers.md) | | Website: landing page, bilingual documentation maintenance, documentation site build and GitHub Pages publication | [Website guide](website/README.md) | -| Installer lifecycle, locking, generated state, managed HTTPS and downloads | [Deployment](deploy/README.md) and [Node installer](deploy/node/README.md) | +| Installer lifecycle, locking, generated state and downloads | [Deployment](deploy/README.md) and [Node installer](deploy/node/README.md) | | Web page behavior and visual rules | [Web product](apps/web/PRODUCT.md), [Web design](apps/web/DESIGN.md) | | Application example behavior and local operation | [Application example](example/parsar/README.md) | @@ -102,7 +102,7 @@ The role needs `CREATE DATABASE`: tests of database-wide state, such as the exec Use official SDKs and upstream types or schemas. Validate raw HTTP payloads and observable workflows alongside SDK behavior. API changes preserve the pinned contracts, [OpenAI compatibility](contracts/agents-api/index.md), official-client tests and Core's independent build. Verify the official-client workflow before application integration. An OpenAI endpoint is a test target only when the required capabilities and credentials are available. -Controlled fixtures and synthetic model responses qualify deterministic behavior. Live acceptance calls a real model API through Core, the daemon and the Harness adapter. Direct native probes establish feasibility only. Keep provider credentials in private test configuration, outside source, logs and task records. +Controlled fixtures and synthetic model responses qualify deterministic behavior. Live acceptance calls a real model API through Core, the daemon and the Harness adapter. Direct native probes establish feasibility only. For wire details the pinned SDK does not specify, probe resources you own and retain the request evidence. In the [OpenAI compatibility](contracts/agents-api/index.md), keep observed behavior distinct from guarantees, accepted profiles distinct from complete coverage, and provider connectivity distinct from deployment qualification. diff --git a/Makefile b/Makefile index 2900c06e0..b812e3a4e 100644 --- a/Makefile +++ b/Makefile @@ -55,7 +55,7 @@ check-go: check-openapi check-runtime-contract: go test ./internal/agentdaemon/proto/... ./services/core/internal/runtimegateway ./apps/daemon/internal/transport ./apps/daemon/internal/dispatch ./apps/daemon/internal/wireconformance -count=1 go test ./services/core/internal/execution -run '^TestRuntimeProtocol' -count=1 - go test ./apps/daemon/internal/agent/... -run '^(TestSharedTextLifecycle|TestPublicHarnessContractDeclarations|TestRegistryRejectsEveryOmittedCapabilityBeforeReplacement|TestUnsupportedExtensionsHaveNoNativeEffects)$$' -count=1 + go test ./apps/daemon/internal/agent/... -run '^(TestSharedTextLifecycle|TestPublicHarnessContractDeclarations|TestRegistryRejectsEveryOmittedCapabilityBeforeReplacement)$$' -count=1 build-daemon: @set -e; output="$${OAC_DEV_HOME:-$$HOME/.oac}/build/daemon"; \ diff --git a/apps/web/.impeccable/design.json b/apps/web/.impeccable/design.json deleted file mode 100644 index ed5f742b6..000000000 --- a/apps/web/.impeccable/design.json +++ /dev/null @@ -1,787 +0,0 @@ -{ - "schemaVersion": 2, - "generatedAt": "2026-09-30T07:33:58.959421+00:00", - "title": "Design System: OpenAgentCore Console", - "extensions": { - "colorMeta": { - "ink": { - "role": "neutral", - "displayName": "Ledger Ink", - "canonical": "oklch(0.247 0.006 258.361)", - "cssVar": "--ink", - "dark": "oklch(0.964 0.002 247.839)", - "tonalRamp": [ - "oklch(0.15 0.006 258.361)", - "oklch(0.27 0.006 258.361)", - "oklch(0.39 0.006 258.361)", - "oklch(0.51 0.006 258.361)", - "oklch(0.63 0.006 258.361)", - "oklch(0.75 0.006 258.361)", - "oklch(0.87 0.006 258.361)", - "oklch(0.95 0.006 258.361)" - ] - }, - "ink-muted": { - "role": "neutral", - "displayName": "Graphite", - "canonical": "oklch(0.506 0.01 264.477)", - "cssVar": "--ink-2", - "dark": "oklch(0.731 0.008 260.731)", - "tonalRamp": [ - "oklch(0.15 0.01 264.477)", - "oklch(0.27 0.01 264.477)", - "oklch(0.39 0.01 264.477)", - "oklch(0.51 0.01 264.477)", - "oklch(0.63 0.01 264.477)", - "oklch(0.75 0.01 264.477)", - "oklch(0.87 0.01 264.477)", - "oklch(0.95 0.01 264.477)" - ] - }, - "ink-subtle": { - "role": "neutral", - "displayName": "Pencil", - "canonical": "oklch(0.695 0.009 264.505)", - "cssVar": "--ink-3", - "dark": "oklch(0.541 0.01 264.484)", - "tonalRamp": [ - "oklch(0.15 0.009 264.505)", - "oklch(0.27 0.009 264.505)", - "oklch(0.39 0.009 264.505)", - "oklch(0.51 0.009 264.505)", - "oklch(0.63 0.009 264.505)", - "oklch(0.75 0.009 264.505)", - "oklch(0.87 0.009 264.505)", - "oklch(0.95 0.009 264.505)" - ] - }, - "sidebar-ink": { - "role": "neutral", - "displayName": "Sidebar Ink", - "canonical": "oklch(0.506 0.01 264.477)", - "cssVar": "--ink-2", - "dark": "oklch(0.731 0.008 260.731)", - "tonalRamp": [ - "oklch(0.15 0.01 264.477)", - "oklch(0.27 0.01 264.477)", - "oklch(0.39 0.01 264.477)", - "oklch(0.51 0.01 264.477)", - "oklch(0.63 0.01 264.477)", - "oklch(0.75 0.01 264.477)", - "oklch(0.87 0.01 264.477)", - "oklch(0.95 0.01 264.477)" - ] - }, - "surface": { - "role": "neutral", - "displayName": "Paper", - "canonical": "oklch(1 0 0)", - "cssVar": "--surface", - "dark": "oklch(0.26 0.006 271.191)", - "tonalRamp": [ - "oklch(0.15 0 0)", - "oklch(0.27 0 0)", - "oklch(0.39 0 0)", - "oklch(0.51 0 0)", - "oklch(0.63 0 0)", - "oklch(0.75 0 0)", - "oklch(0.87 0 0)", - "oklch(0.95 0 0)" - ] - }, - "surface-subtle": { - "role": "neutral", - "displayName": "Margin Gray", - "canonical": "oklch(0.979 0.002 247.839)", - "cssVar": "--inset", - "dark": "oklch(0.243 0.004 264.492)", - "tonalRamp": [ - "oklch(0.15 0.002 247.839)", - "oklch(0.27 0.002 247.839)", - "oklch(0.39 0.002 247.839)", - "oklch(0.51 0.002 247.839)", - "oklch(0.63 0.002 247.839)", - "oklch(0.75 0.002 247.839)", - "oklch(0.87 0.002 247.839)", - "oklch(0.95 0.002 247.839)" - ] - }, - "surface-muted": { - "role": "neutral", - "displayName": "Well Gray", - "canonical": "oklch(0.961 0.001 286.375)", - "cssVar": "--field", - "dark": "oklch(0.293 0.006 271.223)", - "tonalRamp": [ - "oklch(0.15 0.001 286.375)", - "oklch(0.27 0.001 286.375)", - "oklch(0.39 0.001 286.375)", - "oklch(0.51 0.001 286.375)", - "oklch(0.63 0.001 286.375)", - "oklch(0.75 0.001 286.375)", - "oklch(0.87 0.001 286.375)", - "oklch(0.95 0.001 286.375)" - ] - }, - "canvas": { - "role": "neutral", - "displayName": "Canvas", - "canonical": "oklch(0.961 0.002 247.84)", - "cssVar": "--canvas", - "dark": "oklch(0.231 0.004 264.487)", - "tonalRamp": [ - "oklch(0.15 0.002 247.84)", - "oklch(0.27 0.002 247.84)", - "oklch(0.39 0.002 247.84)", - "oklch(0.51 0.002 247.84)", - "oklch(0.63 0.002 247.84)", - "oklch(0.75 0.002 247.84)", - "oklch(0.87 0.002 247.84)", - "oklch(0.95 0.002 247.84)" - ] - }, - "frame-line": { - "role": "neutral", - "displayName": "Card Edge", - "canonical": "color-mix(in oklch, oklch(0.247 0.006 258.361) 11%, transparent)", - "cssVar": "--frame-line", - "dark": "oklch(1 0 0 / 10%)", - "tonalRamp": [] - }, - "line": { - "role": "neutral", - "displayName": "Hairline", - "canonical": "oklch(0.946 0.003 264.542)", - "cssVar": "--line", - "dark": "oklch(0.308 0.006 258.354)", - "tonalRamp": [ - "oklch(0.15 0.003 264.542)", - "oklch(0.27 0.003 264.542)", - "oklch(0.39 0.003 264.542)", - "oklch(0.51 0.003 264.542)", - "oklch(0.63 0.003 264.542)", - "oklch(0.75 0.003 264.542)", - "oklch(0.87 0.003 264.542)", - "oklch(0.95 0.003 264.542)" - ] - }, - "line-muted": { - "role": "neutral", - "displayName": "Faint Rule", - "canonical": "oklch(0.966 0.002 264.542)", - "cssVar": "--line-soft", - "dark": "oklch(0.278 0.006 258.354)", - "tonalRamp": [ - "oklch(0.15 0.002 264.542)", - "oklch(0.27 0.002 264.542)", - "oklch(0.39 0.002 264.542)", - "oklch(0.51 0.002 264.542)", - "oklch(0.63 0.002 264.542)", - "oklch(0.75 0.002 264.542)", - "oklch(0.87 0.002 264.542)", - "oklch(0.95 0.002 264.542)" - ] - }, - "line-strong": { - "role": "neutral", - "displayName": "Firm Rule", - "canonical": "oklch(0.912 0.005 258.326)", - "cssVar": "--line-strong", - "dark": "oklch(0.356 0.007 264.474)", - "tonalRamp": [ - "oklch(0.15 0.005 258.326)", - "oklch(0.27 0.005 258.326)", - "oklch(0.39 0.005 258.326)", - "oklch(0.51 0.005 258.326)", - "oklch(0.63 0.005 258.326)", - "oklch(0.75 0.005 258.326)", - "oklch(0.87 0.005 258.326)", - "oklch(0.95 0.005 258.326)" - ] - }, - "hover": { - "role": "neutral", - "displayName": "Hover", - "canonical": "oklch(0.97 0.002 247.839)", - "cssVar": "--hover", - "dark": "oklch(0.289 0.006 271.22)", - "tonalRamp": [ - "oklch(0.15 0.002 247.839)", - "oklch(0.27 0.002 247.839)", - "oklch(0.39 0.002 247.839)", - "oklch(0.51 0.002 247.839)", - "oklch(0.63 0.002 247.839)", - "oklch(0.75 0.002 247.839)", - "oklch(0.87 0.002 247.839)", - "oklch(0.95 0.002 247.839)" - ] - }, - "pressed": { - "role": "neutral", - "displayName": "Pressed", - "canonical": "oklch(0.933 0.003 247.86)", - "cssVar": "--hover-2", - "dark": "oklch(0.318 0.007 274.747)", - "tonalRamp": [ - "oklch(0.15 0.003 247.86)", - "oklch(0.27 0.003 247.86)", - "oklch(0.39 0.003 247.86)", - "oklch(0.51 0.003 247.86)", - "oklch(0.63 0.003 247.86)", - "oklch(0.75 0.003 247.86)", - "oklch(0.87 0.003 247.86)", - "oklch(0.95 0.003 247.86)" - ] - }, - "tile": { - "role": "neutral", - "displayName": "Tile", - "canonical": "oklch(0.933 0.003 247.86)", - "cssVar": "--hover-2", - "dark": "oklch(0.318 0.007 274.747)", - "tonalRamp": [ - "oklch(0.15 0.003 247.86)", - "oklch(0.27 0.003 247.86)", - "oklch(0.39 0.003 247.86)", - "oklch(0.51 0.003 247.86)", - "oklch(0.63 0.003 247.86)", - "oklch(0.75 0.003 247.86)", - "oklch(0.87 0.003 247.86)", - "oklch(0.95 0.003 247.86)" - ] - }, - "accent": { - "role": "primary", - "displayName": "OpenAgentCore Indigo", - "canonical": "oklch(0.52 0.165 277)", - "cssVar": "--accent", - "dark": "oklch(0.72 0.12 277)", - "tonalRamp": [ - "oklch(0.15 0.165 277)", - "oklch(0.27 0.165 277)", - "oklch(0.39 0.165 277)", - "oklch(0.51 0.165 277)", - "oklch(0.63 0.165 277)", - "oklch(0.75 0.165 277)", - "oklch(0.87 0.165 277)", - "oklch(0.95 0.165 277)" - ] - }, - "accent-emphasis": { - "role": "primary", - "displayName": "Pressed Indigo", - "canonical": "oklch(0.47 0.16 277)", - "cssVar": "--accent-ink", - "dark": "oklch(0.8 0.1 277)", - "tonalRamp": [ - "oklch(0.15 0.16 277)", - "oklch(0.27 0.16 277)", - "oklch(0.39 0.16 277)", - "oklch(0.51 0.16 277)", - "oklch(0.63 0.16 277)", - "oklch(0.75 0.16 277)", - "oklch(0.87 0.16 277)", - "oklch(0.95 0.16 277)" - ] - }, - "accent-fg": { - "role": "primary", - "displayName": "On Indigo", - "canonical": "#ffffff", - "cssVar": "--accent-fg", - "dark": "oklch(0.2 0.03 277)", - "tonalRamp": [] - }, - "data": { - "role": "data", - "displayName": "Data", - "canonical": "oklch(0.56 0.14 277)", - "cssVar": "--data", - "dark": "oklch(0.68 0.13 277)", - "tonalRamp": [ - "oklch(0.15 0.14 277)", - "oklch(0.27 0.14 277)", - "oklch(0.39 0.14 277)", - "oklch(0.51 0.14 277)", - "oklch(0.63 0.14 277)", - "oklch(0.75 0.14 277)", - "oklch(0.87 0.14 277)", - "oklch(0.95 0.14 277)" - ] - }, - "success": { - "role": "signal", - "displayName": "Healthy Green", - "canonical": "oklch(0.6 0.12 158)", - "cssVar": "--green", - "dark": "oklch(0.72 0.12 158)", - "tonalRamp": [ - "oklch(0.15 0.12 158)", - "oklch(0.27 0.12 158)", - "oklch(0.39 0.12 158)", - "oklch(0.51 0.12 158)", - "oklch(0.63 0.12 158)", - "oklch(0.75 0.12 158)", - "oklch(0.87 0.12 158)", - "oklch(0.95 0.12 158)" - ] - }, - "warning": { - "role": "signal", - "displayName": "Caution Amber", - "canonical": "oklch(0.68 0.135 62)", - "cssVar": "--orange", - "dark": "oklch(0.76 0.12 65)", - "tonalRamp": [ - "oklch(0.15 0.135 62)", - "oklch(0.27 0.135 62)", - "oklch(0.39 0.135 62)", - "oklch(0.51 0.135 62)", - "oklch(0.63 0.135 62)", - "oklch(0.75 0.135 62)", - "oklch(0.87 0.135 62)", - "oklch(0.95 0.135 62)" - ] - }, - "danger": { - "role": "signal", - "displayName": "Fault Red", - "canonical": "oklch(0.585 0.17 25)", - "cssVar": "--red", - "dark": "oklch(0.68 0.15 25)", - "tonalRamp": [ - "oklch(0.15 0.17 25)", - "oklch(0.27 0.17 25)", - "oklch(0.39 0.17 25)", - "oklch(0.51 0.17 25)", - "oklch(0.63 0.17 25)", - "oklch(0.75 0.17 25)", - "oklch(0.87 0.17 25)", - "oklch(0.95 0.17 25)" - ] - }, - "status-queued": { - "role": "signal", - "displayName": "Queued Gray", - "canonical": "oklch(0.695 0.009 264.505)", - "cssVar": "--ink-3", - "dark": "oklch(0.541 0.01 264.484)", - "tonalRamp": [ - "oklch(0.15 0.009 264.505)", - "oklch(0.27 0.009 264.505)", - "oklch(0.39 0.009 264.505)", - "oklch(0.51 0.009 264.505)", - "oklch(0.63 0.009 264.505)", - "oklch(0.75 0.009 264.505)", - "oklch(0.87 0.009 264.505)", - "oklch(0.95 0.009 264.505)" - ] - }, - "series-1": { - "role": "data", - "displayName": "Indigo", - "canonical": "oklch(0.56 0.14 277)", - "cssVar": "--series-1", - "dark": "oklch(0.68 0.13 277)", - "tonalRamp": [ - "oklch(0.15 0.14 277)", - "oklch(0.27 0.14 277)", - "oklch(0.39 0.14 277)", - "oklch(0.51 0.14 277)", - "oklch(0.63 0.14 277)", - "oklch(0.75 0.14 277)", - "oklch(0.87 0.14 277)", - "oklch(0.95 0.14 277)" - ] - }, - "series-2": { - "role": "data", - "displayName": "Teal", - "canonical": "oklch(0.7 0.09 195)", - "cssVar": "--series-2", - "dark": "oklch(0.74 0.09 195)", - "tonalRamp": [ - "oklch(0.15 0.09 195)", - "oklch(0.27 0.09 195)", - "oklch(0.39 0.09 195)", - "oklch(0.51 0.09 195)", - "oklch(0.63 0.09 195)", - "oklch(0.75 0.09 195)", - "oklch(0.87 0.09 195)", - "oklch(0.95 0.09 195)" - ] - }, - "series-3": { - "role": "data", - "displayName": "Ochre", - "canonical": "oklch(0.78 0.11 80)", - "cssVar": "--series-3", - "dark": "oklch(0.8 0.1 80)", - "tonalRamp": [ - "oklch(0.15 0.11 80)", - "oklch(0.27 0.11 80)", - "oklch(0.39 0.11 80)", - "oklch(0.51 0.11 80)", - "oklch(0.63 0.11 80)", - "oklch(0.75 0.11 80)", - "oklch(0.87 0.11 80)", - "oklch(0.95 0.11 80)" - ] - }, - "series-4": { - "role": "data", - "displayName": "Coral", - "canonical": "oklch(0.66 0.12 20)", - "cssVar": "--series-4", - "dark": "oklch(0.72 0.11 20)", - "tonalRamp": [ - "oklch(0.15 0.12 20)", - "oklch(0.27 0.12 20)", - "oklch(0.39 0.12 20)", - "oklch(0.51 0.12 20)", - "oklch(0.63 0.12 20)", - "oklch(0.75 0.12 20)", - "oklch(0.87 0.12 20)", - "oklch(0.95 0.12 20)" - ] - }, - "series-5": { - "role": "data", - "displayName": "Slate", - "canonical": "oklch(0.62 0.06 250)", - "cssVar": "--series-5", - "dark": "oklch(0.7 0.06 250)", - "tonalRamp": [ - "oklch(0.15 0.06 250)", - "oklch(0.27 0.06 250)", - "oklch(0.39 0.06 250)", - "oklch(0.51 0.06 250)", - "oklch(0.63 0.06 250)", - "oklch(0.75 0.06 250)", - "oklch(0.87 0.06 250)", - "oklch(0.95 0.06 250)" - ] - }, - "series-6": { - "role": "data", - "displayName": "Sage", - "canonical": "oklch(0.72 0.08 145)", - "cssVar": "--series-6", - "dark": "oklch(0.76 0.08 145)", - "tonalRamp": [ - "oklch(0.15 0.08 145)", - "oklch(0.27 0.08 145)", - "oklch(0.39 0.08 145)", - "oklch(0.51 0.08 145)", - "oklch(0.63 0.08 145)", - "oklch(0.75 0.08 145)", - "oklch(0.87 0.08 145)", - "oklch(0.95 0.08 145)" - ] - }, - "series-other": { - "role": "data", - "displayName": "Series Other", - "canonical": "oklch(0.82 0.008 264)", - "cssVar": "--series-other", - "dark": "oklch(0.45 0.01 264)", - "tonalRamp": [ - "oklch(0.15 0.008 264)", - "oklch(0.27 0.008 264)", - "oklch(0.39 0.008 264)", - "oklch(0.51 0.008 264)", - "oklch(0.63 0.008 264)", - "oklch(0.75 0.008 264)", - "oklch(0.87 0.008 264)", - "oklch(0.95 0.008 264)" - ] - }, - "meter-fill": { - "role": "data", - "displayName": "Meter Fill", - "canonical": "color-mix(in srgb, oklch(0.247 0.006 258.361) 62%, transparent)", - "cssVar": "--meter-fill", - "dark": "color-mix(in srgb, oklch(0.964 0.002 247.839) 62%, transparent)", - "tonalRamp": [] - } - }, - "typographyMeta": { - "metric": { - "displayName": "Metric", - "purpose": "The four Overview tiles; tabular numerals." - }, - "display": { - "displayName": "KPI Figure", - "purpose": "KPI strip values; tabular numerals, one step above body text." - }, - "headline": { - "displayName": "Page Title", - "purpose": "One per page in the page header." - }, - "title": { - "displayName": "Section Title", - "purpose": "Section and card headings; dialog titles step up to 15px." - }, - "body": { - "displayName": "Body", - "purpose": "Table cells, controls, fields, dialog text; document base is 14px/20px." - }, - "label": { - "displayName": "Label", - "purpose": "KPI labels, column headers, text actions, segmented options, the list count." - }, - "mono": { - "displayName": "Mono", - "purpose": "IDs in name cells (Graphite) and other code in tables (Pencil)." - } - }, - "shadows": [ - { - "name": "panel-shadow", - "value": "0 0 0 1px var(--line), 0 1px 2px oklch(0 0 0 / 3%), 0 8px 24px -12px oklch(0 0 0 / 8%)", - "dark": "0 0 0 1px oklch(1 0 0 / 8%), 0 8px 28px -12px oklch(0 0 0 / 50%)", - "purpose": "The white page panel on the canvas." - }, - { - "name": "shadow-card", - "value": "0 0 0 1px var(--frame-line)", - "dark": "0 0 0 1px oklch(1 0 0 / 10%)", - "purpose": "Cards, KPI strips, table frames, chart grids and empty states: a ring only, no drop shadow." - }, - { - "name": "shadow-btn", - "value": "0 0 0 1px var(--line-strong), var(--shadow-xs)", - "dark": "0 0 0 1px oklch(1 0 0 / 0.1), 0 1px 2px oklch(0 0 0 / 0.3)", - "purpose": "Outline buttons, the active segment, and search fields and selects in toolbars." - }, - { - "name": "shadow-hairline", - "value": "0 0 0 1px var(--line)", - "dark": "0 0 0 1px var(--line)", - "purpose": "The ring of inputs and selects inside cards and dialogs, and of count and value pills." - }, - { - "name": "shadow-overlay", - "value": "0 0 0 1px var(--line), var(--shadow-lg)", - "dark": "0 0 0 1px oklch(1 0 0 / 0.15), 0 8px 28px oklch(0 0 0 / 0.34)", - "purpose": "Anchored popovers, menus, dialogs, help tips and chart tooltips." - } - ], - "motion": [ - { - "name": "ease-out-strong", - "value": "cubic-bezier(0.23, 1, 0.32, 1)", - "purpose": "Colour, background and press transitions of buttons, navigation items and segments (150ms); press scales buttons to 0.96." - }, - { - "name": "ease-spring", - "value": "cubic-bezier(0.34, 1.56, 0.64, 1)", - "purpose": "Dialog entry: pop-in from 96% scale in 200ms." - }, - { - "name": "ease-settle", - "value": "cubic-bezier(0.22, 1, 0.36, 1)", - "purpose": "Dialog exit (150ms) and overlay fades." - }, - { - "name": "layout-glide", - "value": "spring, 320ms, no bounce (Motion shared layout)", - "purpose": "The navigation chip and segmented thumbs glide to a new choice." - }, - { - "name": "popover-in", - "value": "opacity 0 to 1, scale 98% to 100%", - "purpose": "Anchored popovers." - }, - { - "name": "page-in", - "value": "opacity 0 to 1, 160ms", - "purpose": "Page change; no choreography." - }, - { - "name": "skeleton-sweep", - "value": "900ms linear, repeating", - "purpose": "First-read skeleton bars; stops under reduced motion." - }, - { - "name": "fleet-link-flow", - "value": "4s linear, repeating", - "purpose": "The moving dash on a live topology link; none on offline links, a stale topology or under reduced motion." - } - ], - "breakpoints": [ - { - "name": "sidebar-rail", - "value": "640px" - }, - { - "name": "min-desktop", - "value": "960px" - }, - { - "name": "stack-charts", - "value": "1180px" - } - ] - }, - "components": [ - { - "name": "Primary Button", - "kind": "button", - "refersTo": "button-primary", - "description": "The one filled button in a header or a non-destructive dialog; ink, not indigo.", - "html": "", - "css": ".ds-btn { display:inline-flex; height:30px; align-items:center; gap:6px; padding:0 13px; font:500 13px/1 \"Inter Variable\", -apple-system, BlinkMacSystemFont, \"PingFang SC\", \"Segoe UI\", \"Microsoft YaHei\", \"Noto Sans SC\", sans-serif; border:0; border-radius:8px; cursor:pointer; transition: transform 150ms cubic-bezier(0.23,1,0.32,1), background-color 150ms cubic-bezier(0.23,1,0.32,1), opacity 150ms cubic-bezier(0.23,1,0.32,1); } .ds-btn-primary { color: var(--canvas); background: var(--ink); box-shadow: inset 0 1px 0 rgb(255 255 255 / 14%), var(--shadow-xs); } .ds-btn-primary:hover { opacity: 0.88; } .ds-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } .ds-btn:active { transform: scale(0.96); }" - }, - { - "name": "Outline Button", - "kind": "button", - "refersTo": "button-outline", - "description": "Every action in a card or section header; Paper with the Firm Rule ring.", - "html": "", - "css": ".ds-btn-outline { display:inline-flex; height:30px; align-items:center; gap:6px; padding:0 13px; font:500 13px/1 \"Inter Variable\", -apple-system, BlinkMacSystemFont, \"PingFang SC\", \"Segoe UI\", \"Microsoft YaHei\", \"Noto Sans SC\", sans-serif; border:0; border-radius:8px; cursor:pointer; transition: transform 150ms cubic-bezier(0.23,1,0.32,1), background-color 150ms cubic-bezier(0.23,1,0.32,1), opacity 150ms cubic-bezier(0.23,1,0.32,1); color: var(--ink); background: var(--surface); box-shadow: var(--shadow-btn); } .ds-btn-outline:hover { background: var(--inset); } .ds-btn-outline:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } .ds-btn-outline:active { transform: scale(0.96); }" - }, - { - "name": "Text Field", - "kind": "input", - "refersTo": "input-field", - "description": "30px filled field with a Hairline ring inside cards and dialogs; focus turns it Paper with an indigo ring.", - "html": "", - "css": ".ds-input { width:240px; height:30px; padding:0 10px; color: var(--ink); font:400 13px/18px \"Inter Variable\", -apple-system, BlinkMacSystemFont, \"PingFang SC\", \"Segoe UI\", \"Microsoft YaHei\", \"Noto Sans SC\", sans-serif; background: var(--field); border:0; border-radius:8px; box-shadow: var(--shadow-hairline); } .ds-input::placeholder { color: var(--ink-3); } .ds-input:hover { background: var(--hover-2); } .ds-input:focus { outline:none; background: var(--surface); box-shadow: 0 0 0 1px var(--accent), 0 0 0 4px var(--accent-tint); }" - }, - { - "name": "Segmented Range Control", - "kind": "chip", - "refersTo": "segmented-option", - "description": "The single style for ranges, order and status filters; the chosen option sits on a Paper thumb.", - "html": "
", - "css": ".ds-seg { display:inline-flex; gap:2px; padding:2px; background: var(--hover-2); border-radius:8px; } .ds-seg button { height:26px; padding:0 11px; color: var(--ink-2); font:500 12.5px/1 \"Inter Variable\", -apple-system, BlinkMacSystemFont, \"PingFang SC\", \"Segoe UI\", \"Microsoft YaHei\", \"Noto Sans SC\", sans-serif; background: transparent; border:0; border-radius:6px; cursor:pointer; } .ds-seg button:hover { color: var(--ink); } .ds-seg button[aria-checked=\"true\"] { color: var(--ink); background: var(--surface); box-shadow: var(--shadow-btn); }" - }, - { - "name": "Sidebar Navigation", - "kind": "nav", - "refersTo": "nav-item", - "description": "Grouped items on the canvas; the active item is a white chip ringed like the page panel.", - "html": "", - "css": ".ds-nav { width:216px; padding:10px 8px; background: var(--canvas); font-family: \"Inter Variable\", -apple-system, BlinkMacSystemFont, \"PingFang SC\", \"Segoe UI\", \"Microsoft YaHei\", \"Noto Sans SC\", sans-serif; } .ds-nav-label { margin:0; padding:14px 8px 6px; color: var(--ink-3); font-size:12px; font-weight:500; } .ds-nav button { display:flex; width:100%; height:32px; align-items:center; gap:8px; margin:0 0 1px; padding:0 8px; color: var(--ink-2); font-size:14px; font-weight:500; background: transparent; border:0; border-radius:8px; cursor:pointer; } .ds-nav button:hover { color: var(--ink); background: var(--hover-2); } .ds-nav button.active { color: var(--ink); background: var(--surface); box-shadow: 0 0 0 1px var(--frame-line), 0 1px 2px oklch(0 0 0 / 4%); }" - }, - { - "name": "KPI Strip", - "kind": "card", - "refersTo": "kpi-cell", - "description": "One card of equal cells divided by inset rules; figures at 20px/500, units small beside them.", - "html": "
Service
Degraded
Sandbox slots
12 / 16
Reported tokens
4.16M
P95 duration
—
", - "css": ".ds-kpis { display:grid; grid-template-columns: repeat(4, minmax(0,1fr)); margin:0; overflow:hidden; background: var(--surface); border-radius:12px; box-shadow: var(--shadow-card); font-family: \"Inter Variable\", -apple-system, BlinkMacSystemFont, \"PingFang SC\", \"Segoe UI\", \"Microsoft YaHei\", \"Noto Sans SC\", sans-serif; } .ds-kpi { display:grid; gap:4px; padding:14px 16px 16px; box-shadow: inset -1px 0 var(--grid-line), inset 0 -1px var(--grid-line); } .ds-kpi dt { color: var(--ink-2); font-size:12.5px; font-weight:500; } .ds-kpi dd { display:flex; align-items:center; gap:8px; margin:0; color: var(--ink); font-size:20px; font-weight:500; line-height:26px; letter-spacing:-0.015em; font-variant-numeric: tabular-nums; } .ds-tone { width:8px; height:8px; border-radius:50%; } .ds-tone.warn { background: var(--orange); }" - }, - { - "name": "Status Dot", - "kind": "custom", - "refersTo": "status-dot", - "description": "A 6px dot plus a plain 13px label; never colour alone, never a pill.", - "html": "Running Needs action Failed Idle", - "css": ".ds-status { display:inline-flex; align-items:center; gap:6px; margin-right:12px; color: var(--ink); font:400 13px/18px \"Inter Variable\", -apple-system, BlinkMacSystemFont, \"PingFang SC\", \"Segoe UI\", \"Microsoft YaHei\", \"Noto Sans SC\", sans-serif; } .ds-status i { width:6px; height:6px; border-radius:50%; background: var(--ink-3); } .ds-status.ok i { background: var(--green); } .ds-status.warn i { background: var(--orange); } .ds-status.danger i { background: var(--red); }" - }, - { - "name": "Meter", - "kind": "custom", - "refersTo": "meter", - "description": "A 5px Well Gray rail with a neutral ink fill that turns amber at 90% and red at 100% of its limit.", - "html": "
Active sandboxes12 / 16
node-0211 / 12
", - "css": ".ds-meter-row { display:grid; grid-template-columns: 120px 1fr auto; align-items:center; gap:12px; margin:6px 0; color: var(--ink-2); font:400 12.5px/18px \"Inter Variable\", -apple-system, BlinkMacSystemFont, \"PingFang SC\", \"Segoe UI\", \"Microsoft YaHei\", \"Noto Sans SC\", sans-serif; } .ds-meter-row b { color: var(--ink); font-weight:500; font-variant-numeric: tabular-nums; } .ds-meter { position:relative; display:block; height:5px; overflow:hidden; background: var(--field); border-radius:999px; box-shadow: inset 0 0 0 1px var(--grid-line); } .ds-meter > span { position:absolute; inset:0 auto 0 0; background: var(--meter-fill); border-radius:999px; } .ds-meter.warn > span { background: var(--orange); }" - }, - { - "name": "Data Table", - "kind": "custom", - "refersTo": "table-row", - "description": "A card with a 36px Paper header over a Hairline, 44px rows divided by Faint Rules, right-aligned tabular numerics.", - "html": "
AgentRequestsError rate
Contract checker571.8%
Code reviewer20—
", - "css": ".ds-table { overflow:auto; background: var(--surface); border-radius:12px; box-shadow: var(--shadow-card); font-family: \"Inter Variable\", -apple-system, BlinkMacSystemFont, \"PingFang SC\", \"Segoe UI\", \"Microsoft YaHei\", \"Noto Sans SC\", sans-serif; } .ds-table table { width:100%; border-collapse:collapse; font-size:13px; line-height:18px; } .ds-table th { height:36px; padding:0 12px; color: var(--ink-2); font-size:12.5px; font-weight:500; text-align:left; background: var(--surface); border-bottom:1px solid var(--line); } .ds-table td { height:44px; padding:6px 12px; color: var(--ink); border-bottom:1px solid var(--grid-line); } .ds-table tr:hover td { background: var(--hover); } .ds-table .n { text-align:right; font-variant-numeric: tabular-nums; }" - } - ], - "narrative": { - "northStar": "The Operator's Ledger", - "overview": "The console is a management tool, not a developer showroom. Every screen reads like a ledger page laid on a desk: a quiet canvas, one white page panel, one header, then white cards holding the evidence (figures, charts, tables). Structure comes from the card edge, 1px internal rules and whitespace; there are no cards inside cards. Colour is spent on problems and on the data itself, almost never on decoration. The indigo accent means \"you selected this\" or \"this is a link\"; its data shade (`--data`) means \"this is the single measured quantity\". Primary actions are filled with ink.\n\nDensity is deliberately high and calm: 13px body, 44px table rows, 30px controls, tabular figures in every column. The system is bilingual (zh-CN and English) and ships light and dark themes on the same token names; dark swaps values, never structure. It honours reduced motion and treats keyboard focus as a first-class state (2px indigo outline).\n\nThe data contract is part of the look. Core reports only what it observes, so the interface shows absence honestly: an em dash, a gap in a line, the word \"Unavailable\" or \"Unknown\". Explanations stay one click away behind a circled question mark, so the page stays a ledger rather than a leaflet.", - "keyCharacteristics": [ - "Each page sits in one white panel with 14px corners on a cool gray canvas, which also holds the sidebar. Inside the panel, cards are drawn by a hairline ring, and the page header is white with a hairline rule under it.", - "One indigo voice for selection, focus, links and single-series data (`--data`); the primary button is ink.", - "Meters are neutral ink; green, amber and red appear only when something is wrong or a state needs reporting.", - "A six-slot categorical palette for multi-series data, bound to the entity, not its rank.", - "One list grammar on every resource page: project filter, search, count, name with compact ID, creator, row actions.", - "Tabular numerals everywhere a number can line up.", - "Status is always a dot plus a plain-language label.", - "Explanations live behind \"?\" help tips; errors, warnings and safety notices stay visible. No small print: an empty state's explanation is a help tip beside its title, a field's rules a help tip beside its label, and filler lines are cut." - ], - "rules": [ - { - "name": "The Colour Only for Problems Rule", - "body": "A healthy state is drawn in ink. Meters fill in neutral ink and turn amber or red only past their thresholds; tone dots appear only on figures that report a state.", - "section": "colors" - }, - { - "name": "The One Voice Rule", - "body": "Indigo is for selection, focus, links and the single `--data` series. Multi-series charts draw from `--series-1..6` and `--series-other`, never from the accent.", - "section": "colors" - }, - { - "name": "The Entity Owns Its Colour Rule", - "body": "A categorical colour follows the entity (model, tool), never its rank. An entity keeps its slot while visible; only slots of entities that left the view are reused. Anything beyond six series collapses into Series Other.", - "section": "colors" - }, - { - "name": "The Columns Line Up Rule", - "body": "Every figure that can share a column uses tabular numerals: KPI and tile values, numeric table cells (right-aligned), legend totals, axis ticks, tooltip values, counts.", - "section": "typography" - }, - { - "name": "The Honest Figure Rule", - "body": "Missing data renders as \"—\", a gap in the line, \"Unavailable\" or \"Unknown\"; never as 0. Compact numbers keep two decimals only when the integer part is a single digit (\"1.04M\"), otherwise one (\"415.7万\"); values under 10,000 print whole. Durations read \"850 ms / 12.4 s / 4m 12s / 3h 5m\".", - "section": "typography" - }, - { - "name": "The Plain Vocabulary Rule", - "body": "zh-CN copy and the zh docs use one term per concept: 项目 (project), 沙箱 (sandbox), 提供商 (provider: 沙箱提供商 for the sandbox provider, 模型提供商 for the model provider, and the MCP provider behind a Credential), 凭据 (any other credential), 执行器 (executor), 主机 (host), 模板 (template), 密钥 (key: Core 密钥, 项目 API 密钥), 创建者 (creator), 已上报 (reported), 活跃 (active). API terms stay in English (Agent, Session, Turn, Item, Runtime, Environment, Harness, Skill, Vault, Credential), and usage counts are \"token\". Time ranges read \"1 小时 / 6 小时 / 24 小时 / 7 天\" (English \"1h / 6h / 24h / 7d\"), always in the one segmented control style.", - "section": "typography" - }, - { - "name": "The Liveness Only Rule", - "body": "The moving dash on a topology link shows that a connection is live, never traffic or work. An offline link is dashed and still, a stale topology shows no movement, and reduced motion stops the animation.", - "section": "layout" - }, - { - "name": "The One Page Grammar Rule", - "body": "Every page uses PageHeader, PageBody and Section from `components/console-ui.tsx`, and every resource list uses the list grammar from `components/list-ui.tsx`. No page invents its own header height, gutter, section rhythm or toolbar.", - "section": "layout" - }, - { - "name": "The One Card Rule", - "body": "Figures, charts and tables sit in one card divided by 1px internal rules. A card never contains another bordered, shadowed container; an empty list is itself one card.", - "section": "elevation" - } - ], - "dos": [ - "**Do** put every explanation of a figure, column, section or page behind a circled \"?\" help tip; report errors in a dialog or a toast; keep warnings and safety notices (deletion consequences, a key shown once) visible.", - "**Do** start every project-scoped toolbar with the project filter, then search, with the count on the right.", - "**Do** end every resource table with the Creator column and then the row actions.", - "**Do** confirm every deletion in ConfirmDialog.", - "**Do** keep meters in neutral ink and let amber and red mean a threshold was crossed.", - "**Do** reserve OpenAgentCore Indigo for selection, focus, links and the single `--data` series.", - "**Do** place figures, charts and tables in one card divided by 1px internal rules.", - "**Do** render missing data as \"—\", a chart gap, \"Unavailable\" or \"Unknown\".", - "**Do** show status as a 6px dot plus a plain label.", - "**Do** use tabular numerals for every aligned figure and right-align numeric columns.", - "**Do** build every page from PageHeader, PageBody and Section." - ], - "donts": [ - "**Don't** add lines of small explanatory print under headings, KPIs, fields or charts.", - "**Don't** colour healthy meters, bars or states; colour is for problems and data.", - "**Don't** colour multi-series data with the indigo accent.", - "**Don't** nest cards inside cards or draw a dashed empty state.", - "**Don't** render an unreported value as 0 or draw a missing interval as a zero line.", - "**Don't** use coloured status pills or colour-only status.", - "**Don't** reassign a categorical colour by rank when data re-sorts.", - "**Don't** show full IDs in list columns; show the compact ID with its copy button.", - "**Don't** add uppercase letter-spaced micro-labels or eyebrow lines above headings; a section is named by its title alone.", - "**Don't** mix synonyms in zh-CN copy (for example alternating 沙盒 with 沙箱, or API 密钥 with API key)." - ] - } -} diff --git a/apps/web/.impeccable/surfaces/src-app-tsx.md b/apps/web/.impeccable/surfaces/src-app-tsx.md deleted file mode 100644 index a3b0b0337..000000000 --- a/apps/web/.impeccable/surfaces/src-app-tsx.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -version: 1 -slug: "src-app-tsx" -primary_target: "src/App.tsx" -related_targets: ["src/ConsoleApp.tsx"] ---- - -# Administrator console (operate) - -Scope: the signed-in console shell and every page behind it, including Getting started on the Overview and the optional console tour. Visitor mode: Operate. Audience: the administrator of one OpenAgentCore deployment. Task: judge health, capacity, usage and failures; inspect and delete project assets; manage projects, keys, nodes and each Harness's default model. Constraints: Web API only (`/core/v1`); missing data stays visibly missing; no small print, explanations live in help tips; API terms stay English in Chinese copy; zh-CN and English, light and dark. - -Information architecture: Monitor (Overview, Core metrics, Agent metrics, Sandbox metrics, Session log) · Resources (Agents, Environment templates, Skills, Files, Vaults) · Platform (Projects and keys, Nodes, System). - -## Direction contract - -THESIS: One calm instrument panel for a whole deployment; every screen speaks one component language so the administrator reads state, not layout. Refuses the assembled dashboard of mismatched widgets and loading spinners. OWN-WORLD: Beautiful UI's foundation: cool near-white canvas, white cards drawn by a hairline ring, neutral ink ramp, 8px-radius buttons (ink primary; only status badges are pills), Inter with CJK system fallback, tabular numerals, semantic tints as condiment; Parsar indigo as the only accent, for selection, links and data. STORY: The administrator lands on health, sees what needs attention, drills into a project, Session or node, and acts (delete, issue, revoke) without waiting on a spinner. FIRST VIEWPORT: Page header with title, filters and refresh on one line; KPI strip; the page's primary card (chart grid, table or topology); nothing above the fold is a loader. FORM: User-pinned world (Beautiful UI + Parsar indigo, 2026-09-24); concept roll skipped because a user-pinned direction beats the roll. FINISH: unreviewed and undocumented is unfinished; this build ends with the finish review, the verdict, DESIGN.md, and every shipping raster carrying its provenance diff --git a/apps/web/DESIGN.md b/apps/web/DESIGN.md index d35739d74..462529674 100644 --- a/apps/web/DESIGN.md +++ b/apps/web/DESIGN.md @@ -277,7 +277,7 @@ A restrained neutral ledger with one indigo voice, three signal colours and a se **Character:** One quiet sans in several weights, sized for dense reading; hierarchy comes from weight and a tight scale, not from a second typeface. Mono appears only for machine identifiers, key prefixes, models and commands. ### Hierarchy -- **Metric** (600, 24px, 32px, -0.015em, tabular): the four Overview tiles. +- **Metric** (600, 28px, 36px, -0.025em, tabular): the four Overview tiles. - **Display** (500, 20px, 26px, -0.015em, tabular): KPI strip figures. Figures stand out by weight and position, one step above body text. - **Headline** (600, 20px, 28px, -0.015em): the page title in the page header; one per page. Detail pages put the back button before it. - **Title** (600, 14px, 20px, -0.01em): section and card headings. Dialog titles are 600 at 15px; empty-state titles 500 at 13.5px. @@ -359,14 +359,14 @@ Every resource list, the Session log and the project list share one grammar: - Sections follow: usage figures in a KPI strip, then tables in cards. - A Session's **History** header holds an outline "Jump to the failed Turn" (with the count when several failed) before the view switch while any Turn failed; it shows the conversation (the Turn table when there are no Items), scrolls the page body to the next failed Turn and focuses it. - An active project's page ends its keys with a **How to call** section (see Dialogs) before its write operations. -- A self-hosted Session's **Executor credentials** section ends with **Connect a host**: a Linux/macOS or PowerShell selector, the one copyable command Core generated for that platform, and a link to the native installation guide. The console shows Core's command as it is and never builds one. The command installs the daemon and its Harnesses, starts it and checks its connection; its authorization expires after 30 minutes. Requirements and reconnection details belong in the title help. Reconnection after credential rotation requires `stop`, replacement of the configured credential file, then `start`; disconnection does not imply process exit. Connection status comes only from Core. When Core has no command, a note replaces it; an archived project shows a note instead. +- A self-hosted Session's **Executor credentials** section ends with **Connect a host**: a Linux/macOS or PowerShell selector, the one copyable command Core generated for that platform, and a link to the native installation guide. The console shows Core's command as it is and never builds one. Requirements and reconnection details belong in the title help. Connection status comes only from Core. When Core has no command, a note replaces it; an archived project shows a note instead. ### Dialogs Dialogs are 448px Paper cards (960px when wide) with 8px corners, a 52px header and a 56px Margin Gray footer separated by Hairlines, and the overlay shadow. They cannot be closed while a request runs. - **ConfirmDialog**: the one grammar for destructive actions. The body states what will be deleted and its consequences; the footer holds Cancel (outline) and the confirm button (danger), whose label changes while busy. Core's reason for a rejection, or an uncertain-outcome warning, appears in red inside the dialog. The Skill page's delete dialogs follow the same grammar; deleting a whole Skill also requires typing its name. Archiving a project says in bold that it can't be undone, then how many active keys it revokes (the project read's count, or more when its loaded key list shows more) and that assets and accepted work stay; with active keys it too requires typing the project's name, shown in mono with its inner spaces kept (surrounding spaces are forgiven, Unicode compared in NFC). While the project list is read again Archive waits; if that read failed, a red line says the count may be out of date and Archive stays disabled. - **Key dialogs**: name fields carry their rules in a help tip and their problem in red underneath. The issued key appears in a read-only field with a copy button, under a notice that it is shown once; only "I've saved this key" dismisses it. Closing the dialog moves the key into a pending notice card on the page. - **Executor credential dialog** (640px): the shown-once notice, then a prompt to save the JSON privately before Done. Download credential file is primary; Copy credential is secondary. Installation commands are not repeated here. Done forgets the credential; closing preserves it in the pending card. The native installer reads the unchanged JSON file: its absolute path is entered during interactive installation or passed with `--credential-file`; tokens never enter command arguments. -- **Add node**: the Limits choice first (Recommended, which leaves the limits for Core to derive from the host once it enrolls, or Custom with the two numbers), then the one-time command in a Terminal block (expiry countdown and Copy command in its header), the three progress steps, and, once the installer's minute passes, an amber card with the reason and a copyable system-service log command. Below, the Host requirements Hairline disclosure is open until this browser has shown it once. Installation requires root or sudo, creates the `oac-node` system service, and serves one Core per host because nodes share the service account. The docker group's root-equivalent access is stated once, in **Use Docker instead of microsandbox?**. Do not expose an ordinary-user installation command or user-service prerequisites. The command downloads from the installation's public URL, never the browser's address, so it works as shown on any host. Until the installation is read, a line says it is being checked; a failed read, a public URL other machines can't use (loopback or not HTTPS), or a console without the provider's node files replaces the limits with one line saying why (the failed read with Try again), and the footer offers nothing to generate. Once the node is ready, while Getting started is open, one line under the green status names the next step (set a default model provider, or finish Getting started) with a text action to Settings → Models and credentials or the Overview. +- **Add node**: the Limits choice first (Recommended, which leaves the limits for Core to derive from the host once it enrolls, or Custom with the two numbers), then the one-time command in a Terminal block (expiry countdown and Copy command in its header), the three progress steps, and, once the installer's minute passes, an amber card with the reason and a copyable system-service log command. Below, the Host requirements Hairline disclosure is open until this browser has shown it once. The docker group's root-equivalent access is stated once, in **Use Docker instead of microsandbox?**. Do not expose an ordinary-user installation command or user-service prerequisites. The command downloads from the installation's public URL, never the browser's address, so it works as shown on any host. Until the installation is read, a line says it is being checked; a failed read, a public URL other machines can't use (loopback or not HTTPS), or a console without the provider's node files replaces the limits with one line saying why (the failed read with Try again), and the footer offers nothing to generate. Once the node is ready, while Getting started is open, one line under the green status names the next step (set a default model provider, or finish Getting started) with a text action to Settings → Models and credentials or the Overview. - **Clean up the host**: after a node is removed, a dialog gives the host's uninstall command in the same Terminal block, a Graphite line that it deletes no sandboxes, volumes or images; the uninstaller prints what it keeps. The command requires root or sudo; there is no user-service alternative. A node enrolled with an earlier Core address adds an "Old Core address gone?" disclosure with the `--force` form. The command, too, downloads from the public URL, which the dialog reads again if it is not at hand: until then one line says it is being checked, a failed read says so with Try again, and a public URL other machines can't use (loopback, or none) gets a line saying the service stays on the host and no command can be given. Done dismisses it and focus returns to the page heading. - **Use Docker?**: choosing a backend that declares cautions in sandbox setup, Docker today, titles the dialog "Use {backend}?" and lists each caution as a plain list item: for Docker, that containers share the host kernel and that the node's account joins the docker group, which is root-equivalent on the host. The footer holds Use Docker (outline) and Back (primary), which takes focus; Back, closing or Escape leaves the choice unmade. - **Edit node**: the name, then the Limits choice, Recommended showing the node's `recommended_capacity` while Core has one, or Custom with the numbers. The Nodes list and a node's Capacity show "Active / limit" for Docker and microsandbox alike, so a saved limit shows where it was set. @@ -376,7 +376,7 @@ Dialogs are 448px Paper cards (960px when wide) with 8px corners, a 52px header Sidebar groups Monitor, Resources and Platform with 12px Pencil group labels; items are 36px rows with an 18px outline icon and primary text. Hover takes the Pressed gray; the active item has a neutral background and an indigo edge. Compact icon-rail rows are 40px tall. The Platform group sits below a hairline. A secondary page (one Session) highlights its parent. The footer holds Show Getting started, then sign-out and the language/theme menu. A detail page's back arrow returns to the page it was opened from (a Skill opened from a template goes back to the template); opened directly, it goes to its list. The arrow is labelled plainly "Back". ### KPI strip and metric tiles -A KPI strip is one card of equal cells separated by inset rules. Each cell: a 12.5px Graphite label with an optional help tip, then the figure at 20px/500 with any unit or limit small beside it, optionally led by an 8px tone dot. Overview uses four separate metric tiles instead: a 13px label with a help tip, the same 20px figure (the service status as a dot and a word), and one 12.5px line of context. Figures ellipsize rather than wrap. Live figures on monitor pages roll their digits to a new value on refresh (NumberFlow) instead of swapping. +A KPI strip is one card of equal cells separated by inset rules. Each cell: a 12.5px Graphite label with an optional help tip, then the figure at 20px/500 with any unit or limit small beside it, optionally led by an 8px tone dot. Overview uses four separate metric tiles instead: a 13px label with a help tip, the Metric figure (the service status as a dot and a word), and one 12.5px line of context. Figures ellipsize rather than wrap. Live figures on monitor pages roll their digits to a new value on refresh (NumberFlow) instead of swapping. ### Help tip An 18px circular button holding a 13px circled "?" in Pencil; hover or open takes Ledger Ink on the Pressed gray. It opens on hover, focus or click (click pins it), closes on Escape, scroll or resize, and renders a dark 12px tooltip (8px corners, overlay shadow, max 288px wide) in a portal. The text also exists in a visually hidden element for assistive technology. @@ -398,53 +398,34 @@ On Overview and Session log, failed reads that leave a section unavailable repla A failed action whose outcome needs a decision (a sandbox change with no answer, a timeout or a 5xx) opens an error dialog with the reason and the next step as its primary button. Failed refreshes and project reads also raise an error toast; other failed actions, Core's clear refusal of a sandbox change among them, are reported there with the reason. A refusal leaves the page usable as it was. Errors inside a dialog or a form stay beside what they concern. Coverage notes (Margin Gray, Hairline ring, 8px corners, 12.5px Graphite) state bounded aggregation. Standing warnings that need action use an amber-tinted line at the top of the page body. On Nodes, this names nodes still bound to an old Core address; each of those nodes' status reads Old address (amber dot) with "Remove and add again" under it in 12px Graphite. Partial-data chips are amber-tinted pills with a help tip. Safety notices (a key shown once, a destructive consequence) stay visible in body text. -A local-only installation has the same amber notice on Overview, Nodes and Settings: other machines cannot connect, followed by Review the public address, which leads to Settings → Service and deployment. Add node is disabled with its reason beside the action, and Getting started leaves its first step to do with the address fix visible. A pending or failed installation read cannot complete that step; a failed read shows Unknown and Retry. - ### Onboarding -Signing in and the console tour share one frame: a dark stage on the left (always dark, whatever the theme) and the task panel on the right, which follows the theme. The stage is the product's one authored moment: a flickering indigo dot grid under slow light rays (Magic UI's flickering grid and light rays), Core as the OpenAgentCore mark on a tile with a travelling border beam, and two orbits of Agents, Sessions, Skills, Vaults, files, templates and machines around it; the OpenAgentCore mark is itself nodes on a ring. Brand copy sits bottom-left in solid ink; it is a paragraph, not a heading, because the panel's title names the task. Signing in asks for one thing, the deployment's Core key, in a single password field; a copyable Docker Compose command to read the key stays visible beneath it, with a reminder to substitute a custom installation directory. The key’s authority stays in a help tip. A refused key, too many attempts or an unavailable console is an error beside the field. Signing in opens the console on the Overview. The optional tour has three chapters — Monitor, Resources, Platform — whose stage shows a real dark screenshot of those pages, tilted towards the panel; it takes the place of the console until its last button, Skip or Escape, and then returns the focus to the control that opened it. Entering the console or the tour, and leaving the tour, happen inside a View Transition: the old page dissolves forward and the new one is revealed in a circle growing from the pressed button. With reduced motion the orbits hold their places, the grid is a still frame and no transition runs. +Signing in and the console tour share one frame: a dark stage on the left (always dark, whatever the theme) and the task panel on the right, which follows the theme. The stage is the product's one authored moment: a flickering indigo dot grid under slow light rays (Magic UI's flickering grid and light rays), Core as the OpenAgentCore mark on a tile with a travelling border beam, and two orbits of Agents, Sessions, Skills, Vaults, files, templates and machines around it; the OpenAgentCore mark is itself nodes on a ring. Brand copy sits bottom-left in solid ink; it is a paragraph, not a heading, because the panel's title names the task. Signing in asks for one thing, the deployment's Core key, in a single password field. The key’s authority stays in a help tip. A refused key, too many attempts or an unavailable console is an error beside the field. Signing in opens the console on the Overview. The optional tour has three chapters — Monitor, Resources, Platform — whose stage shows a real dark screenshot of those pages, tilted towards the panel; it takes the place of the console until its last button, Skip or Escape, and then returns the focus to the control that opened it. Entering the console or the tour, and leaving the tour, happen inside a View Transition: the old page dissolves forward and the new one is revealed in a circle growing from the pressed button. With reduced motion the orbits hold their places, the grid is a still frame and no transition runs. ### Getting started The first card on the Overview while any step is to do: a card header ("Getting started", "n of 4 done", a help tip, then a ghost Take the tour button and an icon button that hides it) over four rows split by Faint Rules. Each row has a 22px numbered ring (a check on the tile wash when done), a 13px/600 title over one 12.5px Graphite line, a status dot (Done in green, To do in Pencil, Checking pending, Unknown for a failed read) and one outline action while the step is to do: Set up sandboxes, Add node, Open Nodes or Open sandbox backend; Open Settings; Create project (which continues to the new project's first key) or Issue key; See how to call (the newest active project, preferring one with an active key), or Projects and keys without an active project. Add node, Create project and Issue key open their page with the dialog already open; Open Settings brings the Models and credentials tab to the top of the page body and focuses the default Harness's Set or Replace; See how to call opens the project and, once its keys, usage and address are read, brings its How to call heading to the top of the page body, focused. Only the page body scrolls; the page header stays. Every step done turns it into one line, "You're set", with Take the tour and Dismiss; it stays, through the tour, until dismissed, and the checklist does not come back on its own. The choice is kept per installation in the browser, also while the deployment cannot be read; Show Getting started, a quiet row above the sidebar's account controls, opens it again at any time. ### Sandbox setup -Setting up hosted sandboxes is a set of pages inside Settings → Sandbox and scheduling, one decision each: where sandboxes run (own machines or E2B), then the backend, or for E2B the Connect E2B page (service, API key, template build and endpoint), then the size of each sandbox (three presets; E2B skips it, since each sandbox takes the template build's size), then a review. Choices are large cards that advance on a click; short indigo dashes show the progress; pages slide and blur across. The backend page compares microsandbox and Docker behind a help tip; microsandbox comes first, preselected (a saved backend stays selected), with a neutral Recommended pill beside its title. Docker takes a confirmation (see Dialogs) once per visit to setup; a saved Docker deployment has already made it. The review states where sandboxes run, the size, each declared connection value (an empty optional one reads Provider default) and the Core address, read-only: it is config.json's `public_url`, and the console never asks for it. A loopback address carries an amber line under it: only the Core machine reaches it. When Core rejects the configuration for it (E2B with a loopback `public_url`), a red-tinted block under the review states the reason and adds Managed in Settings, which leads to Settings → Service and deployment. A save attempt clears the transient E2B key. Initial setup then asks for it again, with a link to that step; an update may leave it blank to keep the committed key. Advanced settings, one link away on the review page of a backend that declares a default size, hold the exact CPU, memory and disk values; E2B has none, so the review offers no link. A change keeps the saved size while the backend stays the same (a saved size outside the presets is offered as Current). Same-backend editing starts at size or the E2B connection with the provider fixed. With the saved key kept, the template build stays an editable text field of the saved value; entering a key loads the build list again. It is an online configuration update, including when older sandboxes remain: existing node identities and resource ownership are retained. Changing the backend or E2B team requires reset and then a new setup. E2B updates can omit the key to retain it; every explicitly entered key takes the verified replacement path and advances the target generation on success, including the same value. Rejections remain inline with a safe reason and a deliberate way back to reset; never infer teams from a key, auto-reset or auto-resubmit. Optional explanations sit behind help tips; errors and safety consequences remain visible. - -### Sandbox lifecycle - -For deployments with a suspension policy, the Nodes list adds Suspended and Retained / limit columns and a node's Capacity strip adds Suspended, Retained / limit and Snapshots; one-line help tips say what each limit counts. Overview adds the suspended total of all registered nodes under its sandbox slots. The idle threshold is edited in Settings → Sandbox and scheduling, where snapshot retention is read-only. Read failures preserve known counts with a visible unconfirmed notice, and absent evidence stays a dash. Allocation rows show elapsed time in the recorded compute phase without estimating expiry. Sandbox metrics shows one Status column, Core's lifecycle record; when an active sandbox has no sample, the reason reads in its CPU and Memory cells, never as a second status. Supporting persistence and resume conditions belong in help tips. - -### Configuration generations -A single rollout row opens a details dialog for Core's target generation, previous-generation sandboxes and rollout counts. Poll rapidly only while Core reports preparing, or while the independent reset is active. Settled is preparation state, not proof that all nodes are ready or all older Sessions have ended. Retained old resources alone must not keep rapid polling alive. Render failed, update-required and unknown target states distinctly. Keep offline/live-provider status separate from a node's durable serving-generation pin; the pin alone never means the node is online or ready. Node detail shows the serving generation and target preparation; allocation detail shows the owned configuration generation. Do not calculate rollout completion from these rows or promise immediate placement on the target. - -A generation-only update within the same installation/backend lifecycle retains compatible previous node/allocation evidence while refreshing. Failed or pending reads visibly qualify those observations; never replace them with fabricated zeros. Reset, backend and installation lifecycle changes discard incompatible data. The shared deployment query and write ownership below govern navigation, late reads, explicit retries and login isolation. +Setting up hosted sandboxes is a set of pages inside Settings → Sandbox and scheduling, one decision each: where sandboxes run (own machines or E2B), then the backend, or for E2B the Connect E2B page (service, API key, template build and endpoint), then the size of each sandbox (three presets; E2B skips it, since each sandbox takes the template build's size), then a review. Choices are large cards that advance on a click; short indigo dashes show the progress; pages slide and blur across. The backend page compares microsandbox and Docker behind a help tip; microsandbox comes first, preselected (a saved backend stays selected), with a neutral Recommended pill beside its title. Docker takes a confirmation (see Dialogs) once per visit to setup; a saved Docker deployment has already made it. The review states where sandboxes run, the size, each declared connection value (an empty optional one reads Provider default) and the Core address, read-only: the console never asks for it. A loopback address carries an amber line under it: only the Core machine reaches it. When Core rejects the configuration for it (E2B with a loopback `public_url`), a red-tinted block under the review states the reason and adds Managed in Settings, which leads to Settings → Service and deployment. A save attempt clears the transient E2B key. Initial setup then asks for it again, with a link to that step; an update may leave it blank to keep the committed key. Advanced settings, one link away on the review page of a backend that declares a default size, hold the exact CPU, memory and disk values; E2B has none, so the review offers no link. A change keeps the saved size while the backend stays the same (a saved size outside the presets is offered as Current). Same-backend editing starts at size or the E2B connection with the provider fixed. With the saved key kept, the template build stays an editable text field of the saved value; entering a key loads the build list again. It is an online configuration update, including when older sandboxes remain: existing node identities and resource ownership are retained. Changing the backend or E2B team requires reset and then a new setup. E2B updates can omit the key to retain it; every explicitly entered key takes the verified replacement path and advances the target generation on success, including the same value. Rejections remain inline with a safe reason and a deliberate way back to reset; never infer teams from a key, auto-reset or auto-resubmit. Optional explanations sit behind help tips; errors and safety consequences remain visible. ### Sandbox reset -Sandbox configuration offers an explicit reset ([semantics](../../contracts/agents-api/sandbox-deployment.md#reset)) through its panels and confirmation dialogs. The confirmation keeps to one concise consequence paragraph, two mode choices, the auto deadline and footer actions. Put cleanup sequencing and preservation details in help tips. Auto clear is selected first, with a one-hour deadline editable from 5 minutes to 24 hours; Force clear and escalation require destructive confirmation. State directly that hosted work is archived, remaining active work may be cancelled, archived Sessions cannot resume and unpersisted workspace contents may be lost. Further consequences live behind the reset impact help tip. +Sandbox configuration offers an explicit reset ([semantics](../../contracts/agents-api/sandbox-deployment.md#reset)) through its panels and confirmation dialogs. The confirmation keeps to one concise consequence paragraph, two mode choices, the auto deadline and footer actions. Put cleanup sequencing and preservation details in help tips. State directly that hosted work is archived, remaining active work may be cancelled, archived Sessions cannot resume and unpersisted workspace contents may be lost. Further consequences live behind the reset impact help tip. -A persistent progress panel uses Core's busy, idle and cleanup counts, deadline and named offline-node blockers. Bring blocked nodes online for confirmed cleanup; never offer a browser-side force-release shortcut. Poll the deployment every five seconds only while its reset is non-null. A passed deadline does not establish force or completion; only a Core response does. Completion opens the setup flow, with a new explicit save using the generation read from Core, including zero on a fresh install. Reset and online configuration rollout have independent authoritative progress; neither automatically replays configuration. +A persistent progress panel uses Core's busy, idle and cleanup counts, deadline and named offline-node blockers. Bring blocked nodes online for confirmed cleanup; never offer a browser-side force-release shortcut. Poll the deployment every five seconds only while its reset is non-null. Read deployment progress independently of node details. Partial failures retain successful facts with a visible stale/unavailable notice. An uncertain write opens the recovery dialog and requires a new authoritative read before another mutation; refresh reads state and never resubmits the write. The connection's QueryClient owns both the authoritative deployment and pending or uncertain writes across route transitions. Leaving Sandbox configuration cannot cancel or forget a submitted reset, and a cached node snapshot cannot replace a newer reset or completion learned on Overview. Returning to Nodes or Sandbox configuration reads the shared deployment immediately and refreshes node evidence separately. Only a successful authoritative read begun after the write settles can release the mutation block; an earlier or still-pending read cannot. Submitting consumes the reset confirmation even if its outcome is uncertain; recovery uses the separate read-and-review dialog. Observation retries preserve applicable non-secret configuration drafts. A changed installation, owner epoch, backend, mode or generation discards the prior draft and confirmation. Logout clears this connection-scoped state. ### Settings page -Settings is the single platform configuration entry, with one tab per [group](../../docs/configuration.md#runtime-settings-web). Each tab has a stable address; links from Nodes and metrics open the owning tab, and the Sandbox and scheduling header links back to Nodes. Forms save independently through their resource APIs, never through one cross-resource Save all action. +Settings is the single platform configuration entry, with one tab per [group](../../docs/configuration.md#runtime-settings-web). Each tab has a stable address; links from Nodes and metrics open the owning tab, and the Sandbox and scheduling header links back to Nodes. The hierarchy is page, tab, section, row. The page header loses its rule to an underline tab row: 40px text tabs in Graphite 13px/500, the current one Ink with a 2px Ink underline. Below 640px the tabs wrap onto a second row rather than scroll out of sight. The body is a left-aligned column at most 880px wide with 32px between sections. A section is the shared Section heading (14px/600, help tip, section-wide outline actions on the right) over one card of label/value rows (`settings.css`): a 200px Graphite label column with its help tip and an Ink value column, 8px by 16px padding, 32px minimum line height, Faint Rules between rows; below 640px the label sits above the value. An action that changes one value sits at the end of its row (Edit tenant capacity; Save beside the idle-seconds field, whose error appears beneath it). Read-only status is a row too (Configuration rollout, Snapshot retention); tables sit in one framed card. Settings never repeats the KPI strips of Nodes or the metrics pages. The irreversible operation closes the page in its own section: a red-ringed card with one Ink line stating the consequence and a red-text outline button. -Sandbox and scheduling contains sandbox setup or Deployment provider (Provider, Each sandbox, the E2B connection rows and the rollout row, with Change resources in the section header), then Automatic suspension (the deployment-wide idle policy and read-only snapshot retention, shown only when the deployment declares suspension), then Reset. While a reset runs, its progress section leads the page with Force reset now and Cancel reset in its header. Nodes owns the nodes themselves: status, per-node limits, allocations, host figures and charts, Add node (with its enrollment form), Edit node and removal. Resource changes never imply rewriting existing allocations. Lower limits do not imply forced termination or snapshot deletion. +Sandbox and scheduling contains sandbox setup or Deployment provider (Provider, Each sandbox, the E2B connection rows and the rollout row, with Change resources in the section header), then Automatic suspension (the deployment-wide idle policy and read-only snapshot retention, shown only when the deployment declares suspension), then Reset. While a reset runs, its progress section leads the page with Force reset now and Cancel reset in its header. -Workspace storage shows the selected configuration's adapter and tenant capacity (in bytes units, or No tenant capacity limit) under Storage configuration, with Select new configuration in the section header and Edit tenant capacity on the capacity row. The configuration ID and parameters JSON sit in a closed Configuration details disclosure at the end of the card. Select new configuration accepts an adapter identifier, configuration ID, parameters JSON object and optional write-only credential JSON object. Edit tenant capacity keeps the current configuration identity. Explain adapter preparation and existing-workspace behavior behind help tips, with a link to the owning configuration document; keep required form labels and validation visible. Never prefill credentials or persist their drafts across navigation. A failed read is not an unconfigured filesystem, and an uncertain write is not a safe automatic retry. +Workspace storage shows the selected configuration's adapter and tenant capacity (in bytes units, or No tenant capacity limit) under Storage configuration, with Select new configuration in the section header and Edit tenant capacity on the capacity row. The configuration ID and parameters JSON sit in a closed Configuration details disclosure at the end of the card. Explain adapter preparation and existing-workspace behavior behind help tips, with a link to the owning configuration document; keep required form labels and validation visible. Models and credentials stacks one default-model card per Harness. Its header carries the Harness name, its [readiness](../../docs/configuration.md#runtime-settings-web) (Ready in green; Model provider not set in amber, with Set as the primary button; Disabled in neutral, with a View startup settings text action), a Default pill, a help tip and the Set, Replace and Clear actions; rows hold model ID, provider protocol, base URL, key presence, token limits when set and update time, and a foot holds Usage details. Set and Replace open one form dialog. The adapter declaration supplies supported protocols and native JSON support without harness-specific branches. Native settings use an optional JSON object editor with formatting and inline syntax errors. Changing model identity clears native JSON; replacing only the required write-only API key does not. Keys are never prefilled and are forgotten when the form closes. Core rejections remain visible in the form; Clear uses ConfirmDialog. Usage details opens Core's observations separately. -Service and deployment shows installation addresses, an Installation details disclosure, closed by default, with the installation ID and source commit, Domain and HTTPS actions, and the startup settings Core loaded. Process settings remain read-only here; their source stays the installation configuration. Each row's label is the setting's environment variable in code style (OAC_LOG_LEVEL), as Core reports it, wrapping after an underscore so a phone keeps all three columns; values show as is, with arrays joined, On or Off for booleans and None for an empty list. How to change a setting in the section header links to the configuration guide. Sensitive settings show only Configured or Not set. Default uses a neutral pill. - -### One place for each task -Each configuration has exactly one editor in Settings; other pages link to it. Resource operations such as Project keys, Session executor credentials and node removal stay on their resource pages. Keep the configuration page flat: resource editors are dialogs and rollout is one status row with a details action. Explanations go in help tips, not small-print helper paragraphs. Errors, uncertain outcomes and current usage exceeding a new limit remain visible without duplicating the workflow. - -### Diagnostic observations -Failure reasons belong beside the failed Session or Turn status. On the Session page the Status fact stays short, and the reason and its next step read as one block under the facts, at reading width. Their first read uses a skeleton; an unavailable reason names that state and puts the read retry beside its help tip. Technical classifications are translated through one catalogue, not shown as raw error codes. Trace Timing labels Core receipt times separately from tool-reported duration; explanations of batched delivery, clock differences and historical gaps live in help tips. - -The self-hosted connection panel names Core's observed state, the bound key and last heartbeat. The bound-key action uses the rotation confirmation and the one-time credential flow. Stale observations cannot complete Run on host. +Service and deployment shows installation addresses, an Installation details disclosure, closed by default, with the installation ID and source commit, and the startup settings Core loaded. Each row's label is the setting's environment variable in code style (OAC_LOG_LEVEL), as Core reports it, wrapping after an underscore so a phone keeps all three columns; values show as is, with arrays joined, On or Off for booleans and None for an empty list. How to change a setting in the section header links to the configuration guide. Sensitive settings show only Configured or Not set. Default uses a neutral pill. ### Loading and motion The console has no spinners and no "Loading…" lines. Reads are cached (TanStack Query) and prefetched on navigation hover, so revisits show data at once and refreshes keep the last data on screen. Only a first read shows a skeleton in the final layout's cards: table rows, a headline strip with chart panels, or a facts card with a table, with a sweep that repeats every 900ms and stops under reduced motion. Work in progress is the Agent's shimmering "Working…" line in the conversation and the breathing pending dot. @@ -480,8 +461,6 @@ Motion reports state and never makes anyone wait: the navigation chip and segmen ## Navigation icons and first use -Use Lucide throughout. Navigation icons are 18px with a 1.75px stroke; shared tool icons use the same stroke at their existing compact sizes. Default navigation icons use secondary ink, selected icons use primary ink. The selected row has a neutral background and a narrow indigo marker. - -Getting started emphasizes the first confirmed incomplete step with the primary button while leaving every step independently available. Only authoritative, successful zero-Session reads replace the activity chart with first-use guidance. Missing and failed reads retain their uncertainty and recovery controls. +Use Lucide throughout. Navigation icons are 18px with a 1.75px stroke; shared tool icons use the same stroke at their existing compact sizes. Default navigation icons use secondary ink, selected icons use primary ink. -Configuration controls show concise labels, values and actions. Validation and request failures remain visible beside the affected action or field. +Getting started emphasizes the first confirmed incomplete step with the primary button while leaving every step independently available. Only authoritative, successful zero-Session reads replace the activity chart with first-use guidance. diff --git a/apps/web/PRODUCT.md b/apps/web/PRODUCT.md index 7c79d41b1..3e81bb143 100644 --- a/apps/web/PRODUCT.md +++ b/apps/web/PRODUCT.md @@ -22,16 +22,14 @@ The console runs beside the administrator's own Core, with execution, files and ## Operating Context -- Paired console (`services/web`): the administrator signs in with the deployment's [Core key](../../docs/getting-started/operations.md#core-key). Sign-in shows the copyable `~/.oac/core/oac core-key --show` command that reads the key on the Core host, with a reminder to substitute a custom installation directory. The browser sends the key only to sign in and keeps only the session cookie; the console server holds the Core key and forwards the Web API (`/core/v1/**`, including sandbox administration under `/core/v1/sandbox/**`). The console never calls `/v1`. -- The Core key is not an Agents API identity and cannot call `/v1`. An administrator who wants to call the Agents API issues a project API key like any other caller. -- `/console/config` reports the node installer (`node_installer`, `node_installer_sha256`), offered only with a 64-hex digest. Native self-hosted installation does not depend on this endpoint. It also lists the providers it has node files for (`node_artifacts`); without the deployment's provider, Add node says so and issues no command. Signing in grants administration, sandbox administration included. +- Paired console (`services/web`): the administrator signs in with the deployment's [Core key](../../docs/getting-started/operations.md#core-key). Sign-in shows the copyable `~/.oac/core/oac core-key --show` command that reads the key on the Core host, with a reminder to substitute a custom installation directory. The console never calls `/v1`. - Chinese and English UI; light and dark themes; reduced motion honored. ## Information Architecture - **Monitor**: Overview (service status, running Sessions, sandbox slots, the Needs attention count for the last 24 hours, 24-hour Session activity, a compact inventory of Core and up to four nodes, prioritizing offline and degraded nodes when the list is full, where Core opens Core metrics, a node opens its page on Nodes and E2B opens Sandbox metrics, the Needs attention table, whose rows name each Session, its project, its one-line reason and last activity and open it, with older ones left to the Session log, usage by project), Core metrics (whether Core can run Turns: execution slots, the Turn queue, its p95 wait and interrupted Turns, connected daemons, database latency, the Core process's CPU and memory, and background jobs; Go heap, goroutines and the database pool sit in a collapsed Diagnostics area), Agent metrics (requests, errors, duration, tokens, models, tools, Agents and API keys for 1 h / 6 h / 24 h / 7 d), Sandbox metrics (hosted Runtimes across projects over time, active against total; a sandbox opens in a dialog with its figures and CPU and memory charts; an E2B deployment adds what Core holds in E2B's cloud), Request log (recent public or administration HTTP requests, status, duration, safe error code and correlation IDs; bounded to the serving Core instance, with manual refresh and no body capture), Session log (every Session, read-only, with a failed Session's reason under its status, opening one Session's history, which jumps to its failed Turns; a self-hosted Session's page also has its environment's executor credentials). Session facts and Agent metrics link to an Agent's page only when that saved resource exists; inline snapshots and snapshots of deleted Agents remain readable without a broken resource link. Agent metrics' failed Turns open the Agent's Sessions in the Session log. - **Resources**: Agents, Environment templates, Skills, Files, Vaults. Each list shows one project or all projects, with a Project column when all are shown and a Creator column naming the creating key. Detail pages show the resource's facts and offer Delete. An empty list says, in one sentence, that applications create these resources through the Agents API, and links to that resource's section of the API guide; Agent metrics does the same for Sessions. -- **Platform**: Projects and keys (projects, their assets and usage, named keys, write history), Nodes (the node list, each node's capacity and limits, host figures and charts, allocations, Add node, Edit node and individual removal operations), and Settings, the single entry for platform configuration. Sandbox and scheduling owns setup, resource edits, rollout, reset and the idle policy, and links to Nodes for nodes and their capacity. Add node on Nodes offers Recommended or Custom limits before issuing its one-time command; installers use Core's public URL and require supported node artifacts. Edit node changes a node's name and limits; removal stays on the node page and offers the host's uninstall command. Models and credentials owns each Harness's default model and shows its [readiness](../../docs/configuration.md#runtime-settings-web), with Set as the primary action when the model provider is not set and a link to the startup settings when it is disabled. Getting started uses the same readiness. Service and deployment owns installation facts (the public and API base URLs, with the installation ID and source commit behind Installation details), Domain and HTTPS actions and read-only startup settings, each named by its environment variable, with a link to the configuration guide's section on changing them. +- **Platform**: Projects and keys (projects, their assets and usage, named keys, write history), Nodes (the node list, each node's capacity and limits, host figures and charts, allocations, Add node, Edit node and individual removal operations), and Settings, the single entry for platform configuration. Sandbox and scheduling owns setup, resource edits, rollout, reset and the idle policy, and links to Nodes for nodes and their capacity. Add node on Nodes offers Recommended or Custom limits before issuing its one-time command; installers use Core's public URL and require supported node artifacts. Edit node changes a node's name and limits; removal stays on the node page and offers the host's uninstall command. Models and credentials owns each Harness's default model and shows its [readiness](../../docs/configuration.md#runtime-settings-web), with Set as the primary action when the model provider is not set and a link to the startup settings when it is disabled. Getting started uses the same readiness. Service and deployment owns installation facts (the public and API base URLs, with the installation ID and source commit behind Installation details) and read-only startup settings, each named by its environment variable, with a link to the configuration guide's section on changing them. - A node whose provider is not ready names the reason as one Provider-neutral readiness class (provider unavailable, host unsupported, provider files or Runtime image missing, a host too small) and its fix in the help tip beside its status, wherever that status shows. - A node enrolled with an earlier Core address gets no new sandboxes, so on the Nodes list and its page its status is Old address, with "Remove and add again", never Available. - **Sandbox reset** is an explicit administrator operation in Settings → Sandbox and scheduling. Auto clear is the default, with a one-hour deadline (5 minutes–24 hours); Force clear requires destructive confirmation. Its semantics are defined in [Reset](../../contracts/agents-api/sandbox-deployment.md#reset); Web shows only Core's progress and completion, and a new configuration is a separate deliberate save. @@ -40,14 +38,13 @@ The console runs beside the administrator's own Core, with execution, files and - **E2B deployments** have no machines: Nodes offers a link to Settings → Sandbox and scheduling. Overview and Sandbox metrics show the sandboxes Core holds in E2B's cloud (running, starting or changing, and the size of each) instead of node capacity, with no Add node action; a sandbox's dialog adds its disk use. - **Automatic suspension** appears when the deployment declares a suspension policy. Nodes shows each node's suspended count and retained limit, the most sandboxes it keeps running and suspended together, with Edit node for its limits; Overview adds the suspended total of all registered nodes under its sandbox slots. Settings → Sandbox and scheduling owns **Automatic suspension**: deployment-wide idle seconds are editable there; snapshot retention remains read-only. These are Core records, not measured CPU or memory savings. Node allocations show the recorded compute phase and elapsed phase time, never an inferred snapshot-expiry countdown. Sandbox metrics' Hosted Runtimes has one Status column: Core's lifecycle record of each sandbox (Active, Suspended, Changing state, Waiting for allocation or Stopped), which Core derives from the allocation's recorded phase. The Session page's Runtime state reads the same way. When an active sandbox has no sample, its CPU and Memory cells name why. Independent workspace persistence is explained only when the deployment declares it. Nodes refreshes its inventory every 30 seconds while visible; reset and active preparation retain their faster polling. - **Getting started**: signing in opens the console on the Overview; nothing is forced first. While a step is to do, a Getting started checklist on the Overview shows four steps, in any order, each with its state and one action: sandboxes ready (a saved deployment and a node online and ready, or a saved E2B deployment), a default model provider on the default Harness (on any enabled Harness when none is default), a project with an active key, and a first Session, whose action opens the call samples of the newest active project, preferring one with an active key. Completion comes from reads the console already makes. It can be hidden; Show Getting started in the sidebar opens it again, and it ends with a brief "You're set". While it is open, Add node ends with the next step once its node is ready: the default model provider while that is to do, otherwise back to the checklist. The optional three-chapter tour of the console (Monitor, Resources, Platform) opens from it, on the sign-in stage. -- Terminology: API terms stay in English in the Chinese UI and docs (Agent, Session, Turn, Item, Runtime, Environment, Skill, Vault, Credential, Harness); providers are 提供商 (沙箱提供商, 模型提供商). The sign-in credential is the Core key ("Core 密钥"); keys issued in a project for applications are project API keys ("项目 API 密钥"). Empty lists read "No … yet" ("还没有…"). Sandbox provider readiness is "Provider not ready / 沙箱提供商未就绪"; revoked credentials and keys use "Revoked / 已撤销". A default model provider is a service address and write-only key; the application's Agent `model` chooses the provider-supported model name. A sandbox is Core-managed compute; Runtime names Core's execution observations, and Environment is the Session's API execution environment. These are distinct counts and resources, not synonyms. +- Terminology: Empty lists read "No … yet" ("还没有…"). Sandbox provider readiness is "Provider not ready / 沙箱提供商未就绪"; revoked credentials and keys use "Revoked / 已撤销". A default model provider is a service address and write-only key; the application's Agent `model` chooses the provider-supported model name. A sandbox is Core-managed compute; Runtime names Core's execution observations, and Environment is the Session's API execution environment. These are distinct counts and resources, not synonyms. ## Capabilities and Constraints - **Projects and keys.** A project owns an isolated set of assets shared by all of its named API keys; projects do not see each other's assets. Issuing or revoking a key never touches assets. The list shows active projects by default; the Archived filter, with its count, shows the rest. Archiving a project revokes every key and keeps its assets viewable and deletable; it can't be undone, so its confirmation says so, counts the active keys it revokes and, when there are any, asks for the project's name. Key plaintext is shown once, at issuance, and never stored by the console. Beside it, and without the key on an active project's page, the console tells developers to set `OPENAI_BASE_URL` (the installation's API base URL) and `OPENAI_API_KEY` (a key of the project), with curl and Python samples that list Agents and create a Session. -- **One home for each setting.** Settings owns each platform configuration editor. Overview, Nodes, metrics and onboarding navigate to its relevant group rather than duplicating forms. Each resource saves through its API independently; there is no cross-resource save. Process settings remain file-owned and read-only in Web. Projects, API keys and Session executor credentials remain on their resource pages. Capacity is node-owned, idle policy is deployment-wide, and sandbox specifications are independent of node limits. Resource editing uses a dialog; rollout counts and generations appear in its details dialog. Settings shows configuration and the status that qualifies it, not capacity figures. Each fact has one page: Nodes owns the node list, per-node capacity and host figures; Overview summarizes capacity and links to Nodes; Sandbox metrics owns Runtime time series and activity; Core metrics owns the Core process. Reset sits apart at the end of Sandbox and scheduling, and leads it while a reset runs. +- **One home for each setting.** Overview, Nodes, metrics and onboarding navigate to the relevant group in [Settings](../../docs/configuration.md#runtime-settings-web) rather than duplicating forms. Capacity is node-owned, idle policy is deployment-wide, and sandbox specifications are independent of node limits. Resource editing uses a dialog; rollout counts and generations appear in its details dialog. Settings shows configuration and the status that qualifies it, not capacity figures. Each fact has one page: Nodes owns the node list, per-node capacity and host figures; Overview summarizes capacity and links to Nodes; Sandbox metrics owns Runtime time series and activity; Core metrics owns the Core process. Reset sits apart at the end of Sandbox and scheduling, and leads it while a reset runs. - **Workspace storage.** Settings → Workspace storage owns selecting an immutable configuration and editing tenant capacity through the storage API. It shows the adapter and tenant capacity; the configuration ID and adapter parameters stay under Configuration details. Adapter parameters and optional write-only credentials are JSON objects validated by Core, with no vendor-specific Web fields or capability discovery by name. Selecting a new configuration does not migrate existing workspaces; changing tenant capacity does not resize them. Credentials are never prefilled, stored in browser persistence or included in error output. Uncertain writes require an authoritative read before another save. -- **Web API only.** Every read and write goes through `/core/v1/**`. The console holds no API key and sends nothing to `/v1`. - **No asset writes except delete.** Assets are created and changed only by a project's keys through the Agents API. The console does not create or edit Agents or Templates, upload Skills or Files, create or replace Credentials, start Sessions, send input or cancel work. Deletion follows the public deletion rules; a busy Session is not deletable and the console never cancels work to make it so. - **Secrets stay write-only.** Credential tokens, Template environment variables and setup commands are never returned, to the administrator included. An Agent's saved model provider shows its protocol, base URL, limits and whether a key is configured, never the key. - **Creators.** Core records the key behind every write. The console shows the creating key of each asset and a project's write history; an asset without a record shows as Unknown. @@ -57,8 +54,8 @@ The console runs beside the administrator's own Core, with execution, files and - **Failure diagnostics.** Failed Session and Turn rows read Core diagnostics and translate its classified reason. On the Session page the Status fact shows the status alone, and the reason and its next step, where the reason has one, read as one block under the facts, such as checking the model shown on the Session page and the Session's model provider; lists show the reason alone, with the next step in its tooltip. A next step must hold for every Session kind and deployment: a self-hosted Session never uses the default model provider, and an E2B deployment has no nodes. Provider observations in Settings show the reason alone. Core never returns the Harness's or provider's own message, so an unclassified failure says so. The console never infers a cause from raw logs. Unavailable or mismatched diagnostics offer an explicit read retry; refreshing does not replay execution. Trace Timing keeps each Item's Core receipt interval separate from public Turn times and native tool duration. Historical missing timestamps stay unknown, negative clock intervals stay missing, and bounded response truncation remains visible. - **Executor credentials.** Core issues executor credentials; the console does so with the deployment's Core key. A Session page whose environment is self-hosted has an Executor credentials section: issue a credential (shown once as one line of JSON, to copy or download, never stored), rotate it (the old one stops working immediately) or revoke it (the executor disconnects; installed Runtime state and workspace contents are not deleted). The file lets one executor connect for that environment only; it cannot call the Agents API. - **Default provider observations.** Each configured Harness offers Usage details for Core's last successful use and any newer classified provider error. Missing records remain unknown; an error at or before the last success is no longer actionable. These are best-effort observations, not readiness checks. Failed refreshes qualify retained records, and replacing the provider starts a new observation history. -- **Host connection.** Core's connection observation and credential metadata share one five-second read while visible. Never connected, connected, disconnected, bound credential revoked, and unknown are distinct; a recent heartbeat alone never proves connectivity. Only a fresh connected read marks Host connected. Stale or failed reads withhold completion. Recovery rotates the bound key, stops the installed daemon, replaces the host credential file and starts the daemon again. -- **Connect a host.** The Linux/macOS and PowerShell commands come from Core's installation read for the Session's environment; the console shows them as they are, with a link to the native installation guide, and never builds one itself. A command downloads the matching installer, installs the chosen Harnesses, starts the daemon and checks its connection. Its authorization expires after 30 minutes; the console reads a fresh one every 20 minutes, and says the command is unavailable when Core has none. No model readiness is implied. Rotating a credential requires stopping the installed daemon, replacing the configured file and starting that same daemon again. A disconnected daemon may still be running; `start` alone does not replace it. +- **Host connection.** Core's connection observation and credential metadata share one five-second read while visible. Never connected, connected, disconnected, bound credential revoked, and unknown are distinct; a recent heartbeat alone never proves connectivity. Only a fresh connected read marks Host connected. Stale or failed reads withhold completion. +- **Connect a host.** The Linux/macOS and PowerShell commands come from Core's installation read for the Session's environment; the console shows them as they are, with a link to the native installation guide, and never builds one itself. A command downloads the matching installer, installs the chosen Harnesses, starts the daemon and checks its connection. Its authorization expires after 30 minutes; the console reads a fresh one every 20 minutes, and says the command is unavailable when Core has none. No model readiness is implied. - **Typed errors.** Every failure the console shows, read or write, follows one rule. Known Core codes use shared bilingual copy and safe typed details. Exact Core field paths attach definite refusals to the relevant input. Other codes show Core's own message in English and name the code in Chinese, since Core's messages are English; a failure without a code names its HTTP status, and one that never reached Core says so. Uncertain write outcomes stay form-level and are never retried automatically. - **Read failures.** A read is tried again once after a 5xx answer or a failed connection, never after a 4xx answer. Overview and Session log distinguish unavailable reads from successful empty results. Failed reads have a visible retry; a first read that fails never mentions earlier data; retained or partial data says it may be incomplete or out of date, and Session filter totals stay missing while any required read has failed. Only successful empty reads show zero. - **Local-only address.** Overview, Nodes and Settings warn when Core reports `local_only` and lead to Settings → Service and deployment to review the public address. Add node is unavailable with a reason; Getting started keeps the first step to do until the public address is fixed. An unread installation address cannot complete that step, and a failed read offers Retry. diff --git a/apps/web/src/features/sandbox/SandboxManagerView.tsx b/apps/web/src/features/sandbox/SandboxManagerView.tsx index 1406a33f0..b174b8365 100644 --- a/apps/web/src/features/sandbox/SandboxManagerView.tsx +++ b/apps/web/src/features/sandbox/SandboxManagerView.tsx @@ -117,7 +117,7 @@ function SandboxManager({ consoleConfig }: { consoleConfig: SandboxConsoleConfig const hostedNodes = snapshot?.deployment.mode === "nodes"; // Nodes share the deployment's Provider, which declares whether sandboxes suspend. const suspends = Boolean(snapshot?.deployment.suspension); - // A node enrolled with another address than Core's current one (config.json's public_url) gets no new sandboxes until it is added again. + // A node enrolled with another address than Core's current one (OAC_PUBLIC_URL) gets no new sandboxes until it is added again. const staleNodes = hostedNodes && snapshot ? nodes.filter((node) => onOldAddress(node, snapshot.deployment.core_url)).map((node) => node.name || node.id) : []; const selected = params.id ? nodes.find((node) => node.id === params.id) : undefined; const editTarget = editId ? nodes.find((node) => node.id === editId) ?? null : null; diff --git a/apps/web/src/features/sandbox/SandboxSetupWizard.tsx b/apps/web/src/features/sandbox/SandboxSetupWizard.tsx index ec83e573d..abd9a6c59 100644 --- a/apps/web/src/features/sandbox/SandboxSetupWizard.tsx +++ b/apps/web/src/features/sandbox/SandboxSetupWizard.tsx @@ -81,7 +81,7 @@ function presetOf(provider: SandboxProvider, resources: SandboxResources): Prese * An update can retain the saved credential. * A Provider that declares cautions takes a confirmation, once per wizard * session; a saved deployment has already made it. - * Core's address is config.json's `public_url`: the review only shows it, and + * Core's address is `OAC_PUBLIC_URL`: the review only shows it, and * a configuration Core rejects for it is explained here, where it was saved. */ type WizardProps = { diff --git a/contracts/agents-api/environment-executor-credentials.md b/contracts/agents-api/environment-executor-credentials.md index cf0febd48..a71761c74 100644 --- a/contracts/agents-api/environment-executor-credentials.md +++ b/contracts/agents-api/environment-executor-credentials.md @@ -95,4 +95,4 @@ A machine reconnects only with its bound `key_id`, rotated to a new secret that ## Model provider -A `self_hosted` Session carries its own model provider; deployment defaults never apply. [Model execution](./model-execution.md) owns the delivery rules. A saved Agent's provider key is delivered to the executor of every `self_hosted` Session created with that Agent in the Project, so anyone who can create `self_hosted` Sessions in the Project and run an executor can read it. +[Model execution](./model-execution.md) owns the delivery rules. A saved Agent's provider key is delivered to the executor of every `self_hosted` Session created with that Agent in the Project, so anyone who can create `self_hosted` Sessions in the Project and run an executor can read it. diff --git a/contracts/agents-api/environments.md b/contracts/agents-api/environments.md index ef1a5ee16..179e3c545 100644 --- a/contracts/agents-api/environments.md +++ b/contracts/agents-api/environments.md @@ -87,7 +87,6 @@ The application owns the machine. It creates the Session with a clean absolute ` - `remote_url` is the daemon WebSocket URL derived from Core's public URL, never from request headers or a daemon address. It names Core's private daemon transport. - Enrollment binds the exact Session, Environment, device and executor key. It creates no allocation and cannot move a Session to another device. - The Session's workspace must equal the `/workspace` alias or the exact canonical directory the Runtime is bound to. Naming a path grants no access to it. -- The Session carries its own model provider; deployment defaults never apply ([model execution](./model-execution.md#saved-defaults-and-precedence)). - Session reads, lists and events return the `self_hosted` output with the Environment ID, workspace and capability directories, never private configuration. `capability_directories` lists the caller's selections; the Runtime's installation locations stay private. - Compute, workspace and files stay the application's. Deleting the Session or revoking the credential denies further access but does not stop native processes; the machine owner stops and cleans up. - The workspace and native history must survive a daemon restart. Losing them never authorizes silent replacement or replay. @@ -106,34 +105,13 @@ The application owns the machine. It creates the Session with a clean absolute ` ## Input admission -Session input goes through `POST /agents/sessions/{id}/events` in ordered batches of 1–64 events. On an Environment-bearing Session, Core reserves input that cannot start yet until the Environment is connected and prepared. +On an Environment-bearing Session, Core reserves input that cannot start yet until the Environment is connected and prepared. [Send input](./sessions-events.md#send-input) and [Initial input at Session creation](./sessions-events.md#initial-input-at-session-creation) define each request's behavior, and [Input errors](./sessions-events.md#input-errors) lists every outcome. Creation without input creates no Turn and no connection action but still requests Environment preparation. -A valid node-backed creation can wait for compute capacity after committing. Initial input uses the same reservation and deadline while waiting; creating without input still requests Environment preparation. [Placement and generation selection](./sandbox-deployment.md#generation-ownership-and-rollout) define the scheduling rules. +A valid node-backed creation can wait for compute capacity after committing. Initial input uses the same reservation and deadline while waiting. [Placement and generation selection](./sandbox-deployment.md#generation-ownership-and-rollout) define the scheduling rules. -### Initial input +Under the Session lock, a retry recovers its original reservation or receipt before Core chooses between the active Turn and a new reservation, and keeps that target during later work. An unlocked read or a retry after a conflict never picks a different path. -Creation accepts initial text as a string or an ordered array of user messages. Omitted or null input creates no Turn and no connection action. - -- The creation transaction stores the initial batch as a reservation; for `self_hosted` it also records a connection action. Only the creation winner inserts them; retries never insert them again. -- A `self_hosted` creation returns the Session and the Environment connection target at once, while the machine is still offline, without waiting for admission. A streamed `self_hosted` creation sends the committed JSON projection as its `created` snapshot, already showing `requires_action` and the connection action, then the committed `requires_action` event. -- A fresh creation stream ends right after the idle state recorded when the admitted Turn ends or the reservation stops being pending, or after a failure. The connection alone clearing the action does not end it. A creation without input ends right after its created snapshot; a same-key stream retry ends at once without events. Later subscribers see only future events and recover history through queries. -- Closing the stream leaves committed work intact; the Worker prepares and admits the input. -- If the initial reservation expires, the Session becomes `failed` with a safe error and no actions, and no Turn is created. The Environment itself is not failed. -- Creation retries keep the original identity, deadline and input. Saved-Agent retries with recorded intent recover before execution admission or source resolution. - -### Later input - -| Batch | Behavior | -| --- | --- | -| Messages, Turn active | Appended to the current Turn through ordered admission and native delivery. No new Turn, preparation or reservation. | -| Messages, Session idle | Reserved; the request waits for the Worker to prepare, admit and claim. | -| Cancellation only | Admitted through the ordinary durable cancellation path. An idle cancellation creates no Turn. A new cancellation conflicts while a reservation is pending; it never cancels pre-Turn input. | -| Function results only | Admitted to the named pending calls with the ordinary result receipts, without a Turn or preparation. Cannot bypass a pending reservation. Function results never become Environment installation metadata. | -| Mixed kinds | Rejected. | - -Core returns 202 only after the batch is durably admitted. For a cancellation, 202 confirms admission, not native completion or process exit. Under the Session lock, a retry recovers its original reservation or receipt before Core chooses between the active Turn and a new reservation, and keeps that target during later work. An unlocked read or a retry after a conflict never picks a different path. - -The waiting request uses bounded pooled operations, outside transactions and execution leases; it never prepares or starts native work. Only this route extends its response write deadline, to six minutes, covering the five-minute database deadline plus response time. Disconnecting stops the wait, not the reservation. [Input errors](./sessions-events.md#input-errors) lists every outcome of the request. +A `POST /v1/agents/sessions/{session_id}/events` request waiting on a reservation uses bounded pooled operations, outside transactions and execution leases; it never prepares or starts native work. Only that route extends its response write deadline, to six minutes, covering the five-minute database deadline plus response time. ### Reservations diff --git a/contracts/agents-api/harness-onboarding.md b/contracts/agents-api/harness-onboarding.md index 695c28a4f..c6f6e8ede 100644 --- a/contracts/agents-api/harness-onboarding.md +++ b/contracts/agents-api/harness-onboarding.md @@ -84,8 +84,6 @@ The reason is a fixed safe string, never submitted content, a credential or raw The wire request carries no working directory. The Runtime checks `local_environment.workspace_directory` against its binding and gives the Harness its bound workspace directory in `LocalEnvironment.WorkspaceRoot`; run the native Harness there. -Workspace capability describes the actual Runtime and resource-owner combination. The Codex and MiniMax resource objects reject native workspace access while the common authorized `localworkspace` owner provides it; Claude can expose native read and list access, and the common owner provides writes. Interface presence alone never selects a resource or advertises support. - The declaration admits public combinations and the Runtime advertises the installed combination; neither replaces schema validation or Project authorization. Native behavior tests must agree with the declarations. An advertised operation that returns Unsupported is a contract violation, never success or grounds for replay. ## Executor and Turn lifetimes @@ -234,7 +232,7 @@ The Runtime's per-Turn limits (settlement wait, receipt send budget and output c Before starting, record the operation set, expected results, exclusions and stopping conditions. A qualification ends when its declared operations pass; it does not expand to match another Harness's feature list. -1. **Contract tests.** Call `agent/contracttest.TextLifecycle` from a test named `TestSharedTextLifecycle` with the adapter's prepared Executor and a deterministic native fixture; `claudesdk/executor_test.go` is the reference. It checks independent Turn streams, native owner and history continuity, durable write and application receipts, stale cancellation and healthy continuation after cancellation. `make check-runtime-contract` runs it together with the shared wire, gateway, transport and dispatcher tests, the declaration completeness check and each adapter's `TestUnsupportedExtensionsHaveNoNativeEffects`. Adapter tests also cover two ordinary Turns sharing one native process or connection and history, cancellation followed by another Turn, stale cancellation and late events, native exit, cleanup failure, input write and application receipts, unknown outcomes and fresh per-Turn usage, function, input and child-observation state. State whether a fixture is controlled or a real provider. +1. **Contract tests.** Call `agent/contracttest.TextLifecycle` from a test named `TestSharedTextLifecycle` with the adapter's prepared Executor and a deterministic native fixture; `claudesdk/executor_test.go` is the reference. It checks independent Turn streams, native owner and history continuity, durable write and application receipts, stale cancellation and healthy continuation after cancellation. `make check-runtime-contract` runs it together with the shared wire, gateway, transport and dispatcher tests and the declaration completeness check. Adapter tests also cover two ordinary Turns sharing one native process or connection and history, cancellation followed by another Turn, stale cancellation and late events, native exit, cleanup failure, input write and application receipts, unknown outcomes and fresh per-Turn usage, function, input and child-observation state. State whether a fixture is controlled or a real provider. 2. **Shared integration.** `TestThirdHarnessPublicOnboarding` runs the synthetic Harness through public Session and input admission, Worker device selection, the real WebSocket gateway, the daemon Registry and Router, neutral events and durable terminal projection. It uses a custom immutable registry in the same `execution.Policy` given to the API handler and the dispatcher, and checks applied input receipts, saved native identity, continuation, cancellation, unsupported optional requests and missing mandatory Runtime support. The fixture has no workspace, MCP or public functions, and its registration stays local to the test. It proves the integration path, not native execution. 3. **Real acceptance.** Use the pinned official Python SDK and raw HTTP against Core, a real provider API, the native Harness and a dedicated database. Verify initial execution, a warm follow-up, cancellation and restart with continuation; record native owner identity and same-condition cold and warm timing. For workspace placements also verify Files and Artifacts, workspace identity, that no credentials appear in public responses and that foreign history is rejected. Synthetic or failed runs never count. The opt-in tests below run the pinned-SDK fixtures in `services/core/tests` against a real daemon and model; each runs when `OAC_TEST_OFFICIAL_SDK_PYTHON`, `OAC_TEST_NATIVE_DAEMON_BIN`, `OAC_TEST_NATIVE_PROOF_DIR` and its private options file are set. The options file is a JSON object with exactly `model` and `model_provider` (the fields of `x_agents_core.model_provider`); the test sets it as the deployment default model provider, which the fixtures' `environment: none` Sessions freeze at creation. 4. **Regression.** Existing Harnesses keep working. Run targeted tests, then `make check`; run `make openapi` after API changes and `make sqlc-generate` after query changes. diff --git a/contracts/agents-api/model-execution.md b/contracts/agents-api/model-execution.md index 2d8200b37..24b57b1e0 100644 --- a/contracts/agents-api/model-execution.md +++ b/contracts/agents-api/model-execution.md @@ -95,7 +95,7 @@ New hosted requests, and requests that omit the inline model, record caller inte ``` - `agent.model` is the exact provider model ID; a supplied value always replaces the deployment model. -- The Session's `x_agents_core` accepts `model_provider`, `harness_config` and `environment` ([Environments](./environments.md#preparation-order)); any other member, such as `sandbox_node_id`, is rejected with 400. Hosted node placement is automatic. +- The Session's `x_agents_core` accepts `model_provider`, `harness_config` and `environment` ([Environments](./environments.md#preparation-order)); any other member is rejected with 400. Hosted node placement is automatic. Unsupported protocol, Harness or Environment combinations are rejected before the Session is created. Provider availability is checked during execution, not by a probe. diff --git a/contracts/agents-api/runtime-observability.md b/contracts/agents-api/runtime-observability.md index 36860bb48..14e04d22a 100644 --- a/contracts/agents-api/runtime-observability.md +++ b/contracts/agents-api/runtime-observability.md @@ -2,7 +2,7 @@ title: "Runtime observability" --- -This is the contributor contract for how Core observes Runtimes and keeps their history. The routes and response fields are in the [Runtime telemetry API](./runtime-observability-api.md). The code lives in `services/core/internal/runtimeobs` (resolution, sources, sampler and export), `internal/runtimehistory` (history queries and the PostgreSQL store) and `internal/runtimeobs/otlpexporter`. +This is the contributor contract for how Core observes Runtimes and keeps their history. The routes and response fields are in the [Runtime telemetry API](./runtime-observability-api.md). The code lives in `services/core/internal/runtimeobs` (resolution, sources, sampler and export), `services/core/internal/runtimehistory` (history queries and the PostgreSQL store) and `services/core/internal/runtimeobs/otlpexporter`. Observations are telemetry. They never create, renew, wake, restore or stop compute, never touch Session activity, and never decide idle time, suspension, admission or execution outcomes. @@ -38,8 +38,6 @@ A sample carries: Every measurement is optional. A present zero is an observed zero; an absent value is unavailable and is never shown or aggregated as zero. Core rejects a sample whose `observed_at` is later than its own clock, whose `started_at` is later than `observed_at`, or whose values are negative, non-finite, a zero capacity or limit, or beyond the JSON safe-integer range. -`lifecycle_state` is derived from the allocation state and compute phase in Core's records, never from a sample: no allocation or `creating` is `pending`; `running` with compute phase `running` or `disabled` is `active`, `suspended` is `sleeping`, and `quiescing`, `suspending`, `restoring` or `waking` is `transitioning`; `cleanup_pending` and `released` are `stopped`. - ## Provider mapping ### Docker diff --git a/contracts/agents-api/sandbox-deployment.md b/contracts/agents-api/sandbox-deployment.md index c9dd555ee..3f3ed1d88 100644 --- a/contracts/agents-api/sandbox-deployment.md +++ b/contracts/agents-api/sandbox-deployment.md @@ -39,7 +39,7 @@ POST and PUT take the same complete selection and require `expected_generation` | `configuration` | The provider's public selectors. E2B: the immutable `template` build and the optional paired `api_url` and `domain`. Docker and microsandbox accept only `{}` or omission | | `credential` | The provider's write-only credential. E2B: `{api_key}`, required at first setup and omitted on PUT to keep the current key; a null or empty key is invalid. Docker and microsandbox reject it | -The request has no Core address. Core derives the deployment's `core_url` from the installation public URL (`public_url` in `config.json`, `OAC_PUBLIC_URL` for Core): the origin nodes and sandbox guests use to reach Core. A request that contains `core_url` is rejected with 400 `invalid_request` like any other unknown member. E2B guests reach Core from E2B's cloud, so an E2B selection is rejected with 409 `sandbox_configuration_error` while the public URL is loopback. Docker and microsandbox selections accept a loopback public URL, which serves only local development because a guest's loopback address does not reach its host. Changing the public URL is an installation change; see [Change the public URL](../../docs/getting-started/public-address.md#changing-the-public-url). +The request has no Core address. Core derives the deployment's `core_url` from the installation public URL, [`OAC_PUBLIC_URL`](../../docs/configuration.md#settings): the origin nodes and sandbox guests use to reach Core. A request that contains `core_url` is rejected with 400 `invalid_request` like any other unknown member. E2B guests reach Core from E2B's cloud, so an E2B selection is rejected with 409 `sandbox_configuration_error` while the public URL is loopback. Docker and microsandbox selections accept a loopback public URL, which serves only local development because a guest's loopback address does not reach its host. Changing the public URL is an installation change; see [Change the public URL](../../docs/getting-started/public-address.md#changing-the-public-url). ### Resources diff --git a/contracts/agents-api/session-diagnostics.md b/contracts/agents-api/session-diagnostics.md index 44853d9ec..63687fa8d 100644 --- a/contracts/agents-api/session-diagnostics.md +++ b/contracts/agents-api/session-diagnostics.md @@ -46,8 +46,6 @@ Provisioning parameters come from the confirmed structured receipt, persisted in The object is `core.turn_diagnostics`, with `session_id`, `turn_id`, the official root Turn `status`, nullable `failure`, `items` and `items_truncated`. A Turn failure has `code`, `params` and nullable `failed_at`, without a source or Turn ID. Only failed Turns have failure details; cancelled and completed Turns never inherit a classification from a private outcome. -Native categories apply only to a failed Turn whose outcome is `engine_failed`, and come from the finite outcome metadata described in [native failure classification](../../docs/runtime-protocol.md#native-failure-classification). `connection_failed` params contain `http_status`, 100 to 599 or null; other native categories have empty params. - `items` contains at most 1000 root Items, ordered by `(created_at, position, id)` ascending like the public Items list. Storage reads at most 1001 rows to detect truncation. Each entry has `item_id`, `started_at`, nullable `completed_at`, and nullable integer `observed_duration_ms`. - `started_at` is Core's first persisted input or event receipt of the Item. diff --git a/contracts/agents-api/zh/environment-executor-credentials.md b/contracts/agents-api/zh/environment-executor-credentials.md index c7449e9f7..d0926cce1 100644 --- a/contracts/agents-api/zh/environment-executor-credentials.md +++ b/contracts/agents-api/zh/environment-executor-credentials.md @@ -1,7 +1,7 @@ --- title: "执行器凭据" source: contracts/agents-api/environment-executor-credentials.md -source_hash: bc6bfa4cdaa4a82694fc3b2936aa50f1e00ae7feecea05f7c67401bb5816f185 +source_hash: 9920c082498a3dd0ff4d7a20bed6ab3c43ed5a46a9a8e52c0efe72ce5683924b --- 执行器凭据允许 `oac-daemon` 为一个 `self_hosted` Environment 注册并连接。它只授权该 Environment 的私有 daemon 传输(`/api/v1/agent-daemon/*`),不授权 `/v1`、`/core/v1`、sandbox node 注册或项目资源。项目的 principal 是其执行 principal。Core 只保存密钥摘要。 @@ -97,4 +97,4 @@ Core 永久拒绝 daemon 时(注册 401/409、永久 WebSocket 拒绝,或 da ## 模型提供商 {#model-provider} -`self_hosted` Session 携带自己的模型提供商,部署默认值不适用。[模型执行](model-execution.md)负责交付规则。保存的 Agent 的模型提供商密钥会交给项目中用该 Agent 创建的每个 `self_hosted` Session 的执行器,因此任何能在该项目创建 `self_hosted` Session 并运行执行器的人都能读取它。 +[模型执行](model-execution.md)负责交付规则。保存的 Agent 的模型提供商密钥会交给项目中用该 Agent 创建的每个 `self_hosted` Session 的执行器,因此任何能在该项目创建 `self_hosted` Session 并运行执行器的人都能读取它。 diff --git a/contracts/agents-api/zh/environments.md b/contracts/agents-api/zh/environments.md index 110138577..b584015e7 100644 --- a/contracts/agents-api/zh/environments.md +++ b/contracts/agents-api/zh/environments.md @@ -1,7 +1,7 @@ --- title: "Environment 与模板" source: contracts/agents-api/environments.md -source_hash: e62ff2b6bc6cee007f1bdbfac5cf6204e2d13552686e7d7c0a424362495e7a52 +source_hash: 52d557886a595276c94f935b22531d84a712ceff0e64bf91e04749eed4366dc1 --- Environment 是 Session 的执行资源,包括 Harness 运行所在的机器、工作区以及已完成准备的能力。Session 通过其 `environment` 配置创建 Environment;不存在独立的 create 调用。Environment 模板是 Session 创建时解析的可复用准备配置。本契约涵盖这两类资源、两种放置方式、输入接纳、能力准备、Skills、Plugins 和 MCP 连接来源。 @@ -89,7 +89,6 @@ Core 在 Session 创建事务中创建 Environment 记录;Session upsert 会 - `remote_url` 是根据 Core 的公共 URL 推导出的 daemon WebSocket URL,绝不根据请求头或 daemon 地址生成。它指定 Core 的私有 daemon 传输通道。 - 注册会将精确的 Session、Environment、设备和执行器凭据绑定在一起。它不会创建任何分配,也无法将 Session 迁移到另一台设备。 - Session 的工作区必须等于 `/workspace` 别名,或等于 Runtime 绑定到的精确规范目录。指定某个路径并不会授予对它的访问权限。 -- Session 携带自己的模型提供商;部署默认值绝不适用([model execution](model-execution.md#saved-defaults-and-precedence))。 - Session 读取、列表和事件会返回带有 Environment ID、工作区及能力目录的 `self_hosted` 输出,但绝不返回私有配置。`capability_directories` 列出调用方选择的内容;Runtime 的安装位置保持私有。 - 计算资源、工作区和文件仍归应用程序所有。删除 Session 或撤销凭据会拒绝后续访问,但不会停止原生进程;机器所有者负责停止和清理。 - 工作区和原生历史必须能在 daemon 重启后继续存在。丢失它们绝不授权进行静默替换或重播。 @@ -108,34 +107,13 @@ Core 在 Session 创建事务中创建 Environment 记录;Session upsert 会 ## 输入接纳 {#input-admission} -Session 输入通过 `POST /agents/sessions/{id}/events` 以每次 1–64 个事件的有序批次提交。对于带 Environment 的 Session,Core 会将尚不能启动的输入预留,直到 Environment 完成连接和准备。 +对于带 Environment 的 Session,Core 会将尚不能启动的输入预留,直到 Environment 完成连接和准备。[发送输入](sessions-events.md#send-input)和[创建 Session 时的初始输入](sessions-events.md#initial-input-at-session-creation)定义每个请求的行为,[输入错误](sessions-events.md#input-errors)列出全部结果。无输入的创建不会创建 Turn 或连接操作,但仍会请求 Environment 准备。 -有效的节点模式创建可以在提交后等待计算容量。初始输入在等待期间仍使用同一 reservation 和期限;无输入创建也会请求 Environment 准备。[Placement 与代次选择](./sandbox-deployment.md#generation-ownership-and-rollout) 定义调度规则。 +有效的节点模式创建可以在提交后等待计算容量。初始输入在等待期间仍使用同一 reservation 和期限。[Placement 与代次选择](./sandbox-deployment.md#generation-ownership-and-rollout) 定义调度规则。 -### 初始输入 {#initial-input} +在 Session 锁内,重试会先恢复其原始预留或回执,然后由 Core 在活动 Turn 与新预留之间选择,并在后续工作中保持该目标。未加锁的读取或冲突后的重试绝不会选择不同路径。 -创建操作接受字符串形式的初始文本,或由用户消息构成的有序数组。省略或传入 null 输入不会创建 Turn 或连接操作。 - -- 创建事务会将初始批次存储为预留;对于 `self_hosted`,还会记录连接操作。只有创建胜者会插入它们;重试绝不会再次插入。 -- `self_hosted` 创建会同时返回 Session 和 Environment 连接目标,即使机器仍处于离线状态,也不等待接纳。流式 `self_hosted` 创建会将已提交的 JSON 投影作为其 `created` 快照发送,其中已经显示 `requires_action` 和连接操作,随后发送已提交的 `requires_action` 事件。 -- 新鲜的创建流会在已接纳 Turn 结束时记录空闲状态,或预留不再处于 pending 时记录空闲状态后立即结束,也会在失败后结束。仅因连接而清除操作不会结束该流。无输入的创建会在发送其 `created` 快照后立即结束;使用相同 key 的流重试会立即结束且不发送任何事件。后续订阅者只能看到未来事件,并通过查询恢复历史。 -- 关闭流不会影响已提交的工作;Worker 仍会准备并接纳输入。 -- 如果初始预留过期,Session 会变为 `failed`,并带有安全错误且不包含任何操作,同时不会创建 Turn。Environment 本身不会变为 `failed`。 -- 创建重试会保留原始身份、截止时间和输入。对于记录了意图的 Saved-Agent,重试会在执行接纳或源解析之前进行恢复。 - -### 后续输入 {#later-input} - -| 批次 | 行为 | -| --- | --- | -| 消息,Turn 处于活动状态 | 通过有序接纳和原生传递追加到当前 Turn。不会创建新 Turn、准备或预留。 | -| 消息,Session 处于空闲状态 | 预留该输入;请求等待 Worker 进行准备、接纳和认领。 | -| 仅取消 | 通过常规持久取消路径接纳。空闲状态下的取消不会创建 Turn。预留处于 pending 时,新的取消会发生冲突;它绝不会取消 Turn 之前的输入。 | -| 仅函数结果 | 使用常规结果回执接纳到指定的 pending 调用,不涉及 Turn 或准备。不能绕过 pending 预留。函数结果绝不会成为 Environment 安装元数据。 | -| 混合类型 | 拒绝。 | - -Core 仅在批次已持久接纳后返回 202。对于取消,202 确认的是接纳,而非原生完成或进程退出。在 Session 锁内,重试会先恢复其原始预留或回执,然后由 Core 在活动 Turn 与新预留之间选择,并在后续工作中保持该目标。未加锁的读取或冲突后的重试绝不会选择不同路径。 - -等待请求使用有界的池化操作,位于事务和执行租约之外;它绝不会准备或启动原生工作。只有此路由会将其响应写入期限延长至六分钟,以涵盖五分钟的数据库期限及响应时间。断开连接会停止等待,但不会取消预留。该请求的全部结果见[输入错误](sessions-events.md#input-errors)。 +等待预留的 `POST /v1/agents/sessions/{session_id}/events` 请求使用有界的池化操作,位于事务和执行租约之外;它绝不会准备或启动原生工作。只有该路由会将其响应写入期限延长至六分钟,以涵盖五分钟的数据库期限及响应时间。 ### 预留 {#reservations} diff --git a/contracts/agents-api/zh/harness-onboarding.md b/contracts/agents-api/zh/harness-onboarding.md index 9df78c657..c160dffb0 100644 --- a/contracts/agents-api/zh/harness-onboarding.md +++ b/contracts/agents-api/zh/harness-onboarding.md @@ -1,7 +1,7 @@ --- title: "Harness 协议" source: contracts/agents-api/harness-onboarding.md -source_hash: 7fc635c711d04760883093996eb6415dde15c5cb42f3c5aed24f8f3cc20e87ab +source_hash: 9be8782db19e5d68342b751f9b9aeaeac4566e3489320294ca06fcebd422bf78 --- **Harness** 是一种运行模型和工具循环的原生代理引擎(Codex、Claude Code、MiniMax Code);**Harness 适配器**将 Runtime 的 Executor 和 Turn 契约转换到该引擎的 SDK 或协议。Harness 协议是 Runtime 与适配器之间的契约:适配器接口及其生命周期义务、声明、注册和验收。本文面向添加或维护 Harness 的开发者。 @@ -86,8 +86,6 @@ func (s *Session) SubmitFunctionResult(context.Context, proto.FunctionResultPayl 线协议请求不携带工作目录。Runtime 将 `local_environment.workspace_directory` 与其绑定进行核对,并通过 `LocalEnvironment.WorkspaceRoot` 向 Harness 提供其绑定的工作区目录;必须在该目录中运行原生 Harness。 -工作区能力描述实际 Runtime 与资源所有者的组合。Codex 和 MiniMax 资源对象拒绝原生工作区访问,而通用的授权 `localworkspace` 所有者提供该访问;Claude 可以公开原生读取和列举访问,通用所有者提供写入。仅仅存在相应接口绝不会选择某个资源或宣称支持。 - 声明对公共组合进行准入,Runtime 宣称已安装的组合;二者都不能替代 schema 验证或项目授权。原生行为测试必须与声明一致。已宣称但返回 Unsupported 的操作属于契约违规,既不是成功,也不能作为重放的依据。 ## Executor 和 Turn 生命周期 {#executor-and-turn-lifetimes} @@ -236,7 +234,7 @@ Runtime 对每个 Turn 的限制(结算等待、回执发送预算和输出通 开始前,记录操作集、预期结果、排除项和停止条件。当其声明的操作通过时,资格认定即结束;它不会扩展为匹配另一个 Harness 的功能列表。 -1. **契约测试。** 在名为 `TestSharedTextLifecycle` 的测试中,使用适配器准备好的 Executor 和确定性的原生夹具调用 `agent/contracttest.TextLifecycle`;`claudesdk/executor_test.go` 是参考实现。它检查独立的 Turn 流、原生所有者和历史连续性、持久化写入与应用回执、过期取消,以及取消后的健康继续执行。`make check-runtime-contract` 会将它与共享线协议、gateway、传输层和调度器测试、声明完整性检查以及每个适配器的 `TestUnsupportedExtensionsHaveNoNativeEffects` 一起运行。适配器测试还覆盖两个普通 Turn 共享一个原生进程或连接和历史、取消后执行另一个 Turn、过期取消和迟到事件、原生退出、清理失败、输入写入与应用回执、未知结果,以及每 Turn 新鲜的 usage、函数、输入和子项观察状态。必须说明夹具是受控夹具还是真实提供商。 +1. **契约测试。** 在名为 `TestSharedTextLifecycle` 的测试中,使用适配器准备好的 Executor 和确定性的原生夹具调用 `agent/contracttest.TextLifecycle`;`claudesdk/executor_test.go` 是参考实现。它检查独立的 Turn 流、原生所有者和历史连续性、持久化写入与应用回执、过期取消,以及取消后的健康继续执行。`make check-runtime-contract` 会将它与共享线协议、gateway、传输层和调度器测试以及声明完整性检查一起运行。适配器测试还覆盖两个普通 Turn 共享一个原生进程或连接和历史、取消后执行另一个 Turn、过期取消和迟到事件、原生退出、清理失败、输入写入与应用回执、未知结果,以及每 Turn 新鲜的 usage、函数、输入和子项观察状态。必须说明夹具是受控夹具还是真实提供商。 2. **共享集成。** `TestThirdHarnessPublicOnboarding` 让合成 Harness 通过公共 Session 和输入准入、Worker 设备选择、真实 WebSocket gateway、daemon Registry 和 Router、中立事件以及持久化终态投影运行。它在与 API 处理程序和调度器相同的 `execution.Policy` 中使用自定义不可变注册表,并检查已应用输入回执、已保存原生身份、继续执行、取消、不受支持的可选请求以及缺少强制 Runtime 支持。该夹具没有工作区、MCP 或公共函数,其注册仅保留在测试本地。它证明的是集成路径,而不是原生执行。 3. **真实验收。** 使用锁定的官方 Python SDK 和针对 Core 的原始 HTTP、真实提供商 API、原生 Harness 以及专用数据库。验证初始执行、热后续执行、取消以及带继续执行的重启;记录原生所有者身份以及相同条件下的冷启动和热运行时间。对于工作区放置方式,还要验证 Files 和 Artifacts、工作区身份、公开响应中未出现凭据,以及外部历史会被拒绝。合成运行或失败运行绝不计入。下面的选择性测试会在 `services/core/tests` 中针对真实 daemon 和模型运行锁定 SDK 夹具;设置 `OAC_TEST_OFFICIAL_SDK_PYTHON`、`OAC_TEST_NATIVE_DAEMON_BIN`、`OAC_TEST_NATIVE_PROOF_DIR` 及其私有选项文件后,每项测试才会运行。选项文件是一个 JSON 对象,恰好包含 `model` 和 `model_provider`(即 `x_agents_core.model_provider` 的字段);测试会将其设置为部署默认模型提供商,而夹具的 `environment: none` Session 会在创建时将其冻结。 4. **回归。** 现有 Harness 必须继续正常工作。先运行定向测试,然后运行 `make check`;API 更改后运行 `make openapi`,查询更改后运行 `make sqlc-generate`。 diff --git a/contracts/agents-api/zh/model-execution.md b/contracts/agents-api/zh/model-execution.md index 2643dd36c..4dd0ed457 100644 --- a/contracts/agents-api/zh/model-execution.md +++ b/contracts/agents-api/zh/model-execution.md @@ -1,7 +1,7 @@ --- title: "模型执行" source: contracts/agents-api/model-execution.md -source_hash: d33f9a9919e52136f8ece0a8480c1db059f2f35aa1cefa852ba1d001cc259fa2 +source_hash: 676dda3abe26188f998a8b0ff1cfa761135d6f04cff19433a5e5bd6ec23e7149 --- 每个 Session 都运行一个 Harness,并使用一个模型提供商。Core 通过三个固定版本上游协议未定义的 Core 扩展来选择它们:`x_agents_core.harness` 选择 Harness,`x_agents_core.model_provider` 提供端点和密钥,`x_agents_core.harness_config` 携带原生模型参数。Core 没有提供商目录、模型别名解析或产品权限模型;除 Session 和已保存 Agent 配置包外,唯一存储的配置包是每个 Harness 的一个 [deployment default](#deployment-defaults)。本文档定义 Harness—模型提供商协议:[`internal/modelprovider/config.go`](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/internal/modelprovider/config.go) 负责验证冻结的提供商连接,每个 Harness 则通过 [`internal/harnessconfig/harness.go`](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/internal/harnessconfig/harness.go) 声明其协议和原生参数。 @@ -97,7 +97,7 @@ Core 从同一个数据库快照读取 Agent 配置和加密配置包;显式 ``` - `agent.model` 是准确的提供商模型 ID;只要提供该值,就始终会替换部署模型。 -- Session 的 `x_agents_core` 接受 `model_provider`、`harness_config` 和 `environment`([Environments](environments.md#preparation-order));任何其他成员,例如 `sandbox_node_id`,都会以 400 拒绝。托管节点放置自动完成。 +- Session 的 `x_agents_core` 接受 `model_provider`、`harness_config` 和 `environment`([Environments](environments.md#preparation-order));任何其他成员都会以 400 拒绝。托管节点放置自动完成。 不受支持的协议、Harness 或 Environment 组合会在创建 Session 前被拒绝。提供商可用性在执行期间检查,而不是通过探测检查。 diff --git a/contracts/agents-api/zh/runtime-observability.md b/contracts/agents-api/zh/runtime-observability.md index 5e9aa7473..e19e67478 100644 --- a/contracts/agents-api/zh/runtime-observability.md +++ b/contracts/agents-api/zh/runtime-observability.md @@ -1,10 +1,10 @@ --- title: "Runtime 可观测性" source: contracts/agents-api/runtime-observability.md -source_hash: de7eefac37eb9cc5935c28e2425efc323f1555771e48758ce9412455e891b52c +source_hash: 0ff8d5a694d934cdc384b859f8192772689c7a5b6599124a2051e978aa7b52d3 --- -这是面向贡献者的契约,规定 Core 如何观测 Runtime 并保留其历史。路由和响应字段见 [Runtime telemetry API](runtime-observability-api.md)。代码位于 `services/core/internal/runtimeobs`(解析、源、采样器和导出)、`internal/runtimehistory`(历史查询和 PostgreSQL 存储)以及 `internal/runtimeobs/otlpexporter`。 +这是面向贡献者的契约,规定 Core 如何观测 Runtime 并保留其历史。路由和响应字段见 [Runtime telemetry API](runtime-observability-api.md)。代码位于 `services/core/internal/runtimeobs`(解析、源、采样器和导出)、`services/core/internal/runtimehistory`(历史查询和 PostgreSQL 存储)以及 `services/core/internal/runtimeobs/otlpexporter`。 观测属于遥测数据。它们绝不会创建、续期、唤醒、恢复或停止计算,绝不会影响 Session 活动,也绝不会决定空闲时间、挂起、准入或执行结果。 @@ -40,8 +40,6 @@ none: tenant_id -> session_id (no Session-owned Runtime instance) 每项测量都是可选的。存在的零值表示观测到零;缺失值表示不可用,绝不会显示或聚合为零。Core 会拒绝以下采样:`observed_at` 晚于 Core 自身时钟,`started_at` 晚于 `observed_at`,或任一值为负数、非有限值、容量或限制值为零,或超出 JSON 安全整数范围。 -`lifecycle_state` 根据 Core 记录中的分配状态和计算阶段派生,绝不会从采样派生:没有分配或状态为 `creating` 时为 `pending`;状态为 `running` 且计算阶段为 `running` 或 `disabled` 时为 `active`;`suspended` 为 `sleeping`;`quiescing`、`suspending`、`restoring` 或 `waking` 为 `transitioning`;`cleanup_pending` 和 `released` 为 `stopped`。 - ## 提供商映射 {#provider-mapping} ### Docker {#docker} diff --git a/contracts/agents-api/zh/sandbox-deployment.md b/contracts/agents-api/zh/sandbox-deployment.md index 3487b491e..45bf4aad2 100644 --- a/contracts/agents-api/zh/sandbox-deployment.md +++ b/contracts/agents-api/zh/sandbox-deployment.md @@ -1,7 +1,7 @@ --- title: "沙箱部署" source: contracts/agents-api/sandbox-deployment.md -source_hash: 279d5e0a8587dd866ff19f4d653fc91ab3f6026cf043bca57af104c34ecfc45c +source_hash: 27e0c5be13ea610bf488553702c6f8eca3e530544855e754789116cdb2ddc703 --- 沙箱部署为 Core 管理的 `openai_hosted` 执行选择沙箱提供商和每个沙箱的资源。PostgreSQL 为每个安装维护一个当前有效选择;Web 和 Core API 写入同一配置。节点文件保存其已安装副本和特定于主机的路径,且不能覆盖其资源。Runtime 不属于该选择:每个节点运行其所属安装验证过的 Runtime。该选择独立于 Harness。部署可以保持未配置状态,没有节点;此时它拒绝托管准入。 @@ -41,7 +41,7 @@ POST 和 PUT 接受相同的完整选择,并要求提供先前 GET 返回的 ` | `configuration` | 提供商的公开选择器。E2B:不可变的 `template` 构建以及可选且配套的 `api_url` 和 `domain`。Docker 和 microsandbox 仅接受 `{}` 或省略 | | `credential` | 提供商的只写凭据。E2B:`{api_key}`,首次设置时必填,在 PUT 中省略以保留当前密钥;null 或空密钥无效。Docker 和 microsandbox 拒绝该字段 | -请求中没有 Core 地址。Core 根据安装公开 URL(`config.json` 中的 `public_url`,Core 对应 `OAC_PUBLIC_URL`)派生部署的 `core_url`:这是节点和沙箱客户机访问 Core 时使用的源地址。包含 `core_url` 的请求会像包含任何其他未知成员一样被拒绝,并返回 400 `invalid_request`。E2B 客户机从 E2B 云访问 Core,因此当公开 URL 为回环地址时,E2B 选择会被拒绝,并返回 409 `sandbox_configuration_error`。Docker 和 microsandbox 选择接受回环公开 URL,但这仅适用于本地开发,因为客户机的回环地址无法访问其主机。更改公开 URL 属于安装变更;参见[更改公开 URL](../../../docs/zh/getting-started/public-address.md#changing-the-public-url)。 +请求中没有 Core 地址。Core 根据安装公开 URL [`OAC_PUBLIC_URL`](../../../docs/zh/configuration.md#settings)派生部署的 `core_url`:这是节点和沙箱客户机访问 Core 时使用的源地址。包含 `core_url` 的请求会像包含任何其他未知成员一样被拒绝,并返回 400 `invalid_request`。E2B 客户机从 E2B 云访问 Core,因此当公开 URL 为回环地址时,E2B 选择会被拒绝,并返回 409 `sandbox_configuration_error`。Docker 和 microsandbox 选择接受回环公开 URL,但这仅适用于本地开发,因为客户机的回环地址无法访问其主机。更改公开 URL 属于安装变更;参见[更改公开 URL](../../../docs/zh/getting-started/public-address.md#changing-the-public-url)。 ### 资源 {#resources} diff --git a/contracts/agents-api/zh/session-diagnostics.md b/contracts/agents-api/zh/session-diagnostics.md index 10f3a336b..c260d4ffc 100644 --- a/contracts/agents-api/zh/session-diagnostics.md +++ b/contracts/agents-api/zh/session-diagnostics.md @@ -1,7 +1,7 @@ --- title: "Session 诊断" source: contracts/agents-api/session-diagnostics.md -source_hash: d2159eddd1995bac506cb753a243b9ac063aba0f87c1854e2f97e885f9bb624f +source_hash: 33b3be90cb3059fd281e318274e8101602787e25fcd19072cf355f9054eff907 --- 管理员通过两个需要 Core 密钥的路由读取 Session 或 Turn 失败的原因,它们报告失败类别和 Item 接收时间。 @@ -48,8 +48,6 @@ source_hash: d2159eddd1995bac506cb753a243b9ac063aba0f87c1854e2f97e885f9bb624f 对象为 `core.turn_diagnostics`,包含 `session_id`、`turn_id`、官方根 Turn `status`、可空的 `failure`、`items` 和 `items_truncated`。Turn 失败包含 `code`、`params` 和可空的 `failed_at`,不包含来源或 Turn ID。只有失败的 Turn 才有失败详情;已取消或已完成的 Turn 不会继承私有结果中的分类。 -原生分类仅适用于结果为 `engine_failed` 的失败 Turn,来自[原生失败分类](../../../docs/zh/runtime-protocol.md#native-failure-classification)中定义的有限结果元数据。`connection_failed` 的 params 包含 `http_status`,值为 100 至 599 或 null;其他原生分类使用空 params。 - `items` 最多包含 1000 个根 Item,与公开 Item 列表一样,按 `(created_at, position, id)` 升序排列。存储最多读取 1001 行以检测截断。每项包含 `item_id`、`started_at`、可空的 `completed_at` 和可空整数 `observed_duration_ms`。 - `started_at` 是 Core 首次持久化该 Item 输入或事件回执的时间。 diff --git a/deploy/README.md b/deploy/README.md index 2b2861bcc..af1b6768b 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -16,11 +16,9 @@ Preparation happens in `.staging`. On entry, the installer removes An existing directory runs the release named by `OAC_REVISION` in its `compose.yaml`. When that is the installer's release, the installer resumes: it keeps the saved settings, pulls only missing images and starts the services the way `oac apply` does. The port check applies only to new directories. Another release is refused. -`install.py` resumes a source-bundle installation of its own release under the same lock; another release is refused, and `--public-url` is required only for a new directory ([Offline hosts](../docs/getting-started/install-options.md#offline-hosts)). - `install.py` prepares a new installation in a `.staging-*` directory inside the installation directory: the node payload, `.env`, `oac` and both Compose files. After `docker compose config` accepts them, it moves them into place, `compose.yaml` last. An earlier failure removes the stage, and the directory too when the run created it, so the same command can be rerun; the next run also removes a stage left by a killed run ([Offline hosts](../docs/getting-started/install-options.md#offline-hosts)). A directory with other contents but no `compose.yaml` is refused. -The host `oac` command also implements `apply`, `core-key`, `rotate-core-key` and `uninstall`. `apply` runs init, which finishes a first start that stopped before it and refuses a data volume committed to another release, then checks Core configuration before starting services and recreating those that changed. Reading the key executes `oac-web core-key` in Web. Rotation runs in the initialization container, where Linux file ownership is identical on every host, then restarts Core and Web. Start, stop and logs use `docker compose`; `oac uninstall` removes the installation, its data volume, the lock and, on Windows, the lock file beside the removed directory. The initialization image verifies and copies bundled node metadata without network access. Its receipt authenticates immutable metadata and credentials; the operator-rotatable Core key and its digest remain mutable. No service receives a Docker socket. Initialization emits structured step logs without credential values; view them with `docker compose logs --timestamps init`. +The host `oac` command also implements `apply`, `core-key`, `rotate-core-key` and `uninstall`. Reading the key executes `oac-web core-key` in Web. The initialization image verifies and copies bundled node metadata without network access. Its receipt authenticates immutable metadata and credentials; the operator-rotatable Core key and its digest remain mutable. No service receives a Docker socket. Initialization emits structured step logs without credential values; view them with `docker compose logs --timestamps init`. Web is the only published service; [Web server](../docs/web/console-server.md#forwarding-to-core) defines what it serves and forwards. HTTPS is terminated by the operator's reverse proxy or hosting platform, which routes to `web:8080`; `OAC_PUBLIC_URL` records that origin. diff --git a/docs/architecture.md b/docs/architecture.md index 6ffe2f5b4..cbc0328b7 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -4,7 +4,7 @@ title: "Architecture" OpenAgentCore is an Agent execution platform that brings Harnesses, models, tools, Sessions and execution environments together through replaceable protocol implementations. It separates orchestration, compute and native execution. Core owns the API and durable state. Sandbox Providers manage compute. Independent workspace filesystem adapters manage durable Environment storage through a separate protocol. A Runtime daemon prepares an Environment and runs the selected Harness, whose native SDK or protocol owns the model and tool loop. -The recommended self-managed deployment is microsandbox with kernel NFS workspace storage. E2B supplies managed compute. Docker runs on nodes without checkpoint or external storage. All use the same Core orchestration and capability checks. Compute and filesystem adapters declare their supported combinations; a deployment choice does not introduce a separate Agent execution flow. The [Sandbox Provider protocol](./sandbox-provider.md) and the [Workspace filesystem protocol](./workspace-provider.md) define their contracts and supported capabilities. +The [sandbox backends](./getting-started/sandbox-backends.md) page compares the deployment options. Compute and filesystem adapters declare their supported combinations; a deployment choice does not introduce a separate Agent execution flow. The [Sandbox Provider protocol](./sandbox-provider.md) and the [Workspace filesystem protocol](./workspace-provider.md) define their contracts and supported capabilities. ```mermaid flowchart TB @@ -68,6 +68,6 @@ sequenceDiagram After the daemon connects, Core checks the available Harness and requested capabilities. The Runtime prepares a workspace Environment and its capability snapshot, then prepares or reuses the Session Executor. Core persists the output, tool interactions and receipts for application reads and events. Completion or cancellation settles the Turn; a healthy Executor can serve the next Turn in the same Environment. -For checkpoint-capable compute, Core automatically suspends idle work and releases active compute capacity after confirmed capture and source stop. New work restores retained memory on an eligible compatible node; independent workspace storage stays attached to the same Environment identity. The [checkpoint lifecycle](./sandbox-provider.md#suspension) owns idle admission, wake and retention, and [checkpoint transfer](./sandbox-provider.md#checkpoint-transfer) defines the shared-store and compatibility requirements. An unreachable node alone never authorizes a replacement writer. +The [checkpoint lifecycle](./sandbox-provider.md#suspension) owns idle admission, wake and retention, and [checkpoint transfer](./sandbox-provider.md#checkpoint-transfer) defines the shared-store and compatibility requirements. -Execution, compute and independent workspace storage have separate lifetimes. Closing an Executor does not itself delete storage. The [workspace filesystem contract](./workspace-provider.md#lifetime-and-filesystem-scope) owns persistent storage retention and explicit deletion; compute checkpoint expiry does not authorize its reclamation. Preparation, connection and execution readiness have distinct states. The [Environment contract](../contracts/agents-api/environments.md) owns preparation, and the [Core–Runtime protocol](./runtime-protocol.md) owns ordering, receipts and failure handling. +The [workspace filesystem contract](./workspace-provider.md#lifetime-and-filesystem-scope) owns persistent storage retention and explicit deletion; compute checkpoint expiry does not authorize its reclamation. The [Environment contract](../contracts/agents-api/environments.md) owns preparation, and the [Core–Runtime protocol](./runtime-protocol.md) owns ordering, receipts and failure handling. diff --git a/docs/assets/console-add-node-en.webp b/docs/assets/console-add-node-en.webp deleted file mode 100644 index 1375e9c80..000000000 Binary files a/docs/assets/console-add-node-en.webp and /dev/null differ diff --git a/docs/assets/console-add-node-zh.webp b/docs/assets/console-add-node-zh.webp deleted file mode 100644 index 9708c3958..000000000 Binary files a/docs/assets/console-add-node-zh.webp and /dev/null differ diff --git a/docs/assets/console-node-ready-en.webp b/docs/assets/console-node-ready-en.webp deleted file mode 100644 index 0b58ee50b..000000000 Binary files a/docs/assets/console-node-ready-en.webp and /dev/null differ diff --git a/docs/assets/console-node-ready-zh.webp b/docs/assets/console-node-ready-zh.webp deleted file mode 100644 index a0dd81b90..000000000 Binary files a/docs/assets/console-node-ready-zh.webp and /dev/null differ diff --git a/docs/concepts.md b/docs/concepts.md index 51cc6d65a..b281a3023 100644 --- a/docs/concepts.md +++ b/docs/concepts.md @@ -32,4 +32,4 @@ Tools run with the permissions of the account that launches the daemon. The daem Credential values, model keys and confidential template data are write-only: application and administrator reads omit them. Conversation text, Skill source and Artifact content are readable resource data, including to administrators. -Public writes record the API key responsible. Administrator writes record a separate audit identity and the target Project. Web's actor label is display-only; Core authorizes the Core key. An audit failure rolls back the write. Reads are not audited, and audit records contain no request bodies, secrets or file contents. The [write provenance](../contracts/agents-api/admin-api.md#write-provenance) and [audit log](../contracts/agents-api/admin-api.md#audit-log) contracts define the stored records. +The [write provenance](../contracts/agents-api/admin-api.md#write-provenance) and [audit log](../contracts/agents-api/admin-api.md#audit-log) contracts define the stored records. diff --git a/docs/configuration.md b/docs/configuration.md index b8d89e049..84c236da6 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -134,10 +134,6 @@ The sibling `.lock` directory is for synchronization; ``. Its services are `init`, `database`, `core` and `web`. Core applies database migrations when it starts. Web serves the console and is the only service with a published port, `OAC_WEB_PORT`; see [Request boundary](./web/console-server.md#request-boundary) for what it forwards. No service receives a Docker socket, and Web and Core have no KVM access. -## Independent workspace storage {#independent-workspace-storage} - -microsandbox requires independent workspace storage, and the only supported combination is with the [kernel NFS adapter](./workspace-provider.md#kernel-nfs-adapter). Select it in **Settings → Workspace storage** (`/core/v1/workspace-storage`, see the [Core administration API](../contracts/agents-api/admin-api.md#workspace-storage)); the selection and its tenant capacity apply to new workspaces, and existing workspaces keep their configuration. [Set up workspace storage](./getting-started/workspace-storage.md) covers the NFS export, the Compose mount, the selection payloads and the quota mode storage server. - ## Appendix: Core environment without the installer Core reads its process environment. Compose interpolates `.env` into it and mounts secrets at the container paths below. When running Core directly, set the file variables to absolute paths readable by the Core process; see the [service guide](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/services/core/README.md). diff --git a/docs/getting-started/install-options.md b/docs/getting-started/install-options.md index 00326e29d..d4fcf4ce9 100644 --- a/docs/getting-started/install-options.md +++ b/docs/getting-started/install-options.md @@ -40,7 +40,7 @@ Install with `--host 127.0.0.1`, point your reverse proxy at Web and set `--publ ## Docker Compose and hosting platforms -Use the `compose.yaml` from a release on any [supported Core host](./install.md#prerequisites). The release pins its initialization, Core, Web and PostgreSQL images and source revision. PostgreSQL 16 is the official image, pinned by digest and mirrored to GHCR with the other images. It starts PostgreSQL, Core and Web. Data lives in a named Docker volume. The one-time initialization service generates random secrets there and prepares the node installer; Core applies database migrations when it starts. [Compose configuration](../configuration.md#compose-installations) owns the settings and the data directory. +Use the `compose.yaml` from a release on any [supported Core host](./install.md#prerequisites). The release pins its initialization, Core, Web and PostgreSQL images and source revision. [Compose configuration](../configuration.md#compose-installations) owns the settings and the data directory. For a local trial, download `compose.yaml` from a release into an empty directory, then run: @@ -49,9 +49,7 @@ docker compose up -d --wait --wait-timeout 900 docker compose exec web oac-web core-key ``` -`oac-web core-key` prints the generated Core key to your terminal without writing it to container logs. Open `http://localhost:8080` and use that key to sign in. All installation secrets are generated automatically; keep the same Compose project and its data volume when restarting. - -The initialization image contains only the small node installation metadata alongside the initialization command. First startup verifies and copies that metadata without downloading the control archive or requiring access to GitHub Releases. Later starts verify the saved files. Container images still need to be pulled. An interrupted first initialization can be rerun; an existing database with missing installation secrets is refused. +Open `http://localhost:8080` and sign in with the printed Core key. You can deploy before choosing a domain: leave `OAC_PUBLIC_URL` unset or empty, then follow [Compose configuration](../configuration.md#compose-installations) to set it and redeploy once the platform's domain is ready. The initial localhost origin allows services to start; Web accepts the configured host only, so platform-domain access becomes available after that redeployment. diff --git a/docs/getting-started/nodes.md b/docs/getting-started/nodes.md index 32f6c51e6..8e7fddf68 100644 --- a/docs/getting-started/nodes.md +++ b/docs/getting-started/nodes.md @@ -45,7 +45,6 @@ Node installation and removal require root. Web's command uses `sudo` unless the Copy the current command from **Nodes → Add node**. It includes this installation's address, checksum and enrollment token. Before downloading, it checks Linux amd64, Python 3.9+ and the required tools. Temporary download failures retry up to three attempts. If checksum verification fails, copy a fresh command from Web and retry. - It downloads into the invoking account's private `~/.oac/node-bootstrap` directory, checks SHA-256, and runs with `sudo`, or directly in a root shell. Each retry clears the unfinished download while holding a lock; verified `.pyz` files remain available to installers. -- The token reaches the installer on standard input, so it never appears in a process argument, an environment variable or sudo's log. - The leading space keeps the command out of the shell history where `HISTCONTROL` ignores such lines (the Debian and Ubuntu default). The installer shows each phase as it runs and, once Core confirms the node, a summary with status and log commands. `export NO_COLOR=1` disables colors; the command passes this through sudo as `--no-color`. It never prints tokens. @@ -86,7 +85,6 @@ Root only prepares the account, the group and the unit; everything else, the Doc - Rerunning the same command is safe. Once the node is registered, a rerun uses the node's own credential, changes nothing that already matches and needs no token. It changes nothing and says why when the node's retained identity or node program belongs to another Core address or installation, or when the sandbox configuration has changed since the node registered (the node prepares it itself; see [Change the sandbox configuration](#change-the-sandbox-configuration)): choose **Remove node** on **Nodes**, uninstall the node on the host, then add it again with **Add node**. - A run that fails before the node registers leaves nothing that Core knows. The next command, even after a configuration change, replaces the earlier attempt's configuration and any downloaded file that no longer matches, keeps the files that match and continues. -- A command that already expired, or was used on another host, fails at once with `The enrollment command expired or was already used`: generate a new command and run it within an hour. - If the command expires during a slow download, registration fails with `The enrollment command expired or was already used`. The downloaded files are kept: generate a new command in Web and run it, and the download continues where it stopped. - Every file shows its progress. Failed or interrupted downloads keep what arrived, and a rerun resumes each partial file where it stopped (`Resuming …`), reuses verified complete files and downloads the rest. If the server cannot resume, the file starts again from the beginning. A transfer that brings less than 64 KiB in a minute stops; check the network and rerun the command. - The Docker Runtime image is about 400 MB and the microsandbox files about 500 MB. On a slow link with Docker, load the image first: copy the release's `oac--linux-amd64-runtime.tar.gz` asset to the host and run `sudo docker load -i` on it. The installer then finds the exact image and skips the download. @@ -106,7 +104,7 @@ Sandbox settings apply to the whole installation; nodes follow them. - **Capacity and idle suspension.** On **Nodes**, use **Edit node** for a node's active and retained limits, or **Use recommended** to fill in the recommendation. In **Settings** → **Sandbox and scheduling**, use **Automatic suspension** → **Idle before suspension (seconds)** → **Save** for the deployment-wide [idle policy](../../contracts/agents-api/sandbox-deployment.md#suspension-policy). - **Backend.** On the same page, choose **Reset deployment**. Auto reset archives idle hosted Sessions at once and lets running work finish until the deadline you set; force cancels it now. Offline nodes must come back so Core can confirm their cleanup. When the reset completes, Core has retired every node and unused command: set up the new backend, then add nodes again. **Cancel reset** stops the remaining work; archived Sessions stay archived. -The [reset contract](../../contracts/agents-api/sandbox-deployment.md#generation-ownership-and-rollout) describes what reset archives and keeps. To archive a single hosted Session, use the [Core API](../../contracts/agents-api/admin-api.md#session-archive). +The [reset contract](../../contracts/agents-api/sandbox-deployment.md#reset) describes what reset archives and keeps. To archive a single hosted Session, use the [Core API](../../contracts/agents-api/admin-api.md#session-archive). ## Checkpoint recovery across nodes diff --git a/docs/getting-started/public-address.md b/docs/getting-started/public-address.md index e7e3fa61b..de37ced82 100644 --- a/docs/getting-started/public-address.md +++ b/docs/getting-started/public-address.md @@ -6,7 +6,7 @@ Set the one origin that browsers, nodes, sandboxes and applications use to reach ## Choose the public URL -`OAC_PUBLIC_URL` is the origin applications, nodes, sandboxes and self-hosted executors use. Web accepts only its host. Core derives its other addresses from it, listed under [`OAC_PUBLIC_URL`](../configuration.md#appendix-core-environment-without-the-installer). It is an http or https origin without a path. +`OAC_PUBLIC_URL` is the origin applications, nodes, sandboxes and self-hosted executors use. Web accepts only its host, so DNS rebinding cannot reach it. Core derives its other addresses from it, listed under [`OAC_PUBLIC_URL`](../configuration.md#appendix-core-environment-without-the-installer). It is an http or https origin without a path. | Situation | Public URL | | --- | --- | @@ -100,18 +100,10 @@ TLS verification stays on everywhere. With a private certificate authority, node ### Try it locally with a quick tunnel {#try-it-locally-with-a-quick-tunnel} -A [Cloudflare quick tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/do-more-with-tunnels/trycloudflare/) gives a trial installation with external ingress a temporary public HTTPS address. It forwards to one port, so put a local proxy with the same routes in front: +A [Cloudflare quick tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/do-more-with-tunnels/trycloudflare/) gives a trial installation with external ingress a temporary public HTTPS address. -```caddyfile -http://:8443 { - bind 127.0.0.1 - reverse_proxy 127.0.0.1:8080 -} -``` - -1. Start the proxy: `caddy run --config Caddyfile`. -2. Start the tunnel: `cloudflared tunnel --url http://127.0.0.1:8443`. It prints an address such as `https://random-words.trycloudflare.com`. -3. Set that address as `OAC_PUBLIC_URL` in `~/.oac/core/.env` and run `~/.oac/core/oac apply`. +1. Start the tunnel: `cloudflared tunnel --url http://127.0.0.1:8080`. It prints an address such as `https://random-words.trycloudflare.com`. +2. Set that address as `OAC_PUBLIC_URL` in `~/.oac/core/.env` and run `~/.oac/core/oac apply`. The address changes whenever `cloudflared` restarts; nodes bound to the old address must then be added again. Throughput is low, so a node's first Runtime download (about 500 MB) can be slow; see [slow links](./nodes.md#rerun-expiry-and-slow-links). diff --git a/docs/getting-started/workspace-storage.md b/docs/getting-started/workspace-storage.md index db15e059e..1f1ac793f 100644 --- a/docs/getting-started/workspace-storage.md +++ b/docs/getting-started/workspace-storage.md @@ -4,7 +4,7 @@ title: "Set up workspace storage" Keep Session workspaces on an NFS filesystem outside the sandbox nodes, with or without per-workspace quotas. microsandbox runs every Environment on this storage, so select it before you configure the microsandbox backend. -The supported combination is microsandbox with the [kernel NFS adapter](../workspace-provider.md#kernel-nfs-adapter). Docker and E2B keep their own disks and do not support it; see [Choose a sandbox backend](./sandbox-backends.md). The [Configuration reference](../configuration.md#independent-workspace-storage) lists where the setting lives. +The supported combination is microsandbox with the [kernel NFS adapter](../workspace-provider.md#kernel-nfs-adapter). Docker and E2B keep their own disks and do not support it; see [Choose a sandbox backend](./sandbox-backends.md). ```mermaid flowchart LR diff --git a/docs/maintainers.md b/docs/maintainers.md index 452f9caf6..3969fdf1c 100644 --- a/docs/maintainers.md +++ b/docs/maintainers.md @@ -193,8 +193,6 @@ The planner compares the PR event's tested merge commit with its verified first Compose template and Compose test changes select both `distribution` fixtures and the `compose` smoke job; Core, Web, shared Go packages and the image Dockerfiles also select the smoke job. Run `python3 scripts/compose-smoke.py` locally with Docker available to repeat it. The script uses a unique project, an automatically assigned loopback port and artifacts under `~/.oac/tests/`; it removes its containers and volumes on exit. CI also performs cleanup after a failed or interrupted smoke step. Diagnostics show container status without printing HTTP response bodies or sign-in keys. Core, Web and the ingress image are built from the checkout, with node metadata for the checkout's commit; Web serves a placeholder page instead of the console build, and the initialization container runs with networking disabled. The smoke matrix runs on native Linux amd64 and arm64 runners; the native matrix builds and tests the shared Core installer on Linux, macOS and Windows. -`scripts/ci_plan.py` selects groups from the changed paths. Each selected set includes `hygiene`, mixed changes accumulate their consumers, and every job reads the same plan instead of keeping its own path list. A notification-only PR skips database, browser and native jobs; a notification plus Core change adds backend and API checks. - | Changed input | Groups selected | | --- | --- | | Published documentation and assets under `docs/` and `contracts/`, and `docs.json` | `hygiene`, `website` | @@ -219,13 +217,13 @@ Do not duplicate path lists in reusable workflows or put a `paths` filter on the The final `check` runs even when planning or a dependency fails. It requires a successful, valid plan, every selected job to be successful, and every unselected job to be skipped. Failure, cancellation, a missing job, an unexpected skip or an unexpected execution fails the gate. API/native reusable workflows are direct dependencies of this gate. A newer run on the same PR cancels its predecessor. Release checks run at their requested immutable ref; native packaging executes once inside those checks, and the distribution build waits for them. -Use **Actions → core-check → Run workflow** for a manual full check. For a transient failure, use GitHub's **Re-run failed jobs** so successful jobs remain completed. PR updates cancel the superseded run through Actions concurrency. Build and dependency caches speed execution; they do not stand in for successful tests. Native release installers are passed between jobs using Actions artifacts, within the same release workflow. +Use **Actions → core-check → Run workflow** for a manual full check. For a transient failure, use GitHub's **Re-run failed jobs** so successful jobs remain completed. Native release installers are passed between jobs using Actions artifacts, within the same release workflow. The `CI review and Feishu notification` workflow runs once after a PR merges into main. It checks out the merged commit, reads that PR's existing checks and logs, and sends one Feishu card through `FEISHU_WEBHOOK_URL` that summarizes the change, its fit with the repository rules and the actual CI status. It does not trigger another test run, and a failed review or delivery never fails the job or blocks merges. Closing an unmerged PR does not invoke the review. The workflow uses `pull_request_target` only for the merged event and never checks out an unmerged PR head with notification credentials. -Browser jobs own separate fixtures and servers; increasing workers against the shared mutable fixture is unsafe. Failed browser jobs retain reports/traces for seven days. Native failure phase summaries are retained for seven days and detailed output stays in the Actions logs; credentials and temporary installation trees are not uploaded. Native archives are built and uploaded only for explicit manual packaging or releases, without recompressing the compressed archive. Release distribution artifacts retain their recovery policy; failed publication can reuse the original build as described above. +Browser jobs own separate fixtures and servers; increasing workers against the shared mutable fixture is unsafe. Failed browser jobs retain reports/traces for seven days. Native failure phase summaries are retained for seven days and detailed output stays in the Actions logs; credentials and temporary installation trees are not uploaded. Native archives are built and uploaded only for explicit manual packaging or releases, without recompressing the compressed archive. -The local Node composite action installs the pinned pnpm and caches its package store by lockfile, OS, architecture, Node version and pnpm version. It caches downloaded packages, not `node_modules`; installs remain frozen. Go partitions retain the module/compiler caches described under [publication](#publish-a-version). Cache hits seed work and never replace tests. The three native platforms run independently; parallel execution reduces elapsed time without reducing total machine time. +The local Node composite action installs the pinned pnpm and caches its package store by lockfile, OS, architecture, Node version and pnpm version. It caches downloaded packages, not `node_modules`; installs remain frozen. Go partitions retain the module/compiler caches described under [publication](#publish-a-version). The three native platforms run independently; parallel execution reduces elapsed time without reducing total machine time. For a local change, inspect the selected groups and choose focused checks according to [Checks for a change](../CONTRIBUTING.md#checks-for-a-change): diff --git a/docs/runtime-protocol.md b/docs/runtime-protocol.md index 435ee21a3..b1bd39c8b 100644 --- a/docs/runtime-protocol.md +++ b/docs/runtime-protocol.md @@ -40,8 +40,8 @@ sequenceDiagram R->>C: ordered envelopes ``` -- The [Machine connection API](../contracts/agents-api/machine-api.md#credentials) lists the credential kinds. Project API keys and the Core key are never Runtime credentials. -- Both requests carry the credential as a Bearer header. A credential never appears in a URL, a payload log or a trace. +- The [Machine connection API](../contracts/agents-api/machine-api.md#credentials) lists the credential kinds. +- A credential never appears in a URL, a payload log or a trace. - The first heartbeat is sent at once, then at the interval that bootstrap returned. Each heartbeat declares `supported_agent_kinds`, their availability and their [capabilities](#capability-declarations). Before the first heartbeat, capabilities are unknown; a kind missing from a heartbeat is not advertised. Neither permits inference. - Heartbeats establish liveness only, never execution progress or a receipt for an earlier message. @@ -329,7 +329,6 @@ Every number in this protocol is stated here. The Harness side of the Turn lifet | 2 minutes | The `begin`–`commit` staging phase of one `runtime_prepare` transfer | The transfer is rejected. | | 64 KiB | One chunk of a `runtime_prepare` transfer or a `workspace_write` body | A larger chunk fails validation. | | 50 MiB | One `runtime_prepare` transfer or `workspace_write` body (private bound) | The transfer is rejected before it reaches the installer. | -| 5 MiB | The decoded inline body of a public write, checked by the API before any Runtime work | The API rejects the request. | | 4 MiB | One inbound transport frame | The frame is refused. | | 8 KiB, 128 bytes | A `workspace_read` request payload, and a correlation ID, checked before admission | The request is rejected; an oversized ID is not echoed and oversized trace metadata is omitted from replies. | | 1 MiB | The raw result of a `workspace_read`, within the transport frame | The result is explicitly truncated. | diff --git a/docs/sandbox-provider.md b/docs/sandbox-provider.md index 84c96ca1a..798edfd7a 100644 --- a/docs/sandbox-provider.md +++ b/docs/sandbox-provider.md @@ -132,9 +132,9 @@ A new provider takes these steps: 2. Declare its `sandbox.DeploymentPolicy`. The adapter writes no specification or resource validator. 3. Implement `sandbox.ConfigurationAdapter` over a typed native configuration. `DecodeInput` strictly parses the separate public `configuration` and write-only `credential` objects of a request. `Encode` produces whitelisted public selectors, read-only observations and separate secret bytes, and never passes request JSON through. `Decode` restores stored selectors, and keeps access to owned resources, without remote admission or new template validation. `Normalize` copies its input before changing it. `ResolveChange`, `Equal` and `WithCredential` own inheritance, identity and credential composition. `Requirements` declares whether a credential and a public Core origin are required, and which setup operations are supported: `Discovery` for `DiscoverConfiguration`, `SelectionDiscovery` for `DiscoverSelection` and `CredentialVerification` for `VerifyCredential`. `DiscoverConfiguration` takes a `ConfigurationDiscoveryQuery{Field, Path}`, where `Field` names a declared `Discovered` field and `Path` holds the values chosen at the earlier levels, and returns `ConfigurationOptions`: a list of `{Value, Label, Resources, Complete}`, where `Complete` marks an option that can be saved as the field's value and the others lead to a deeper level. It validates the query and returns a safe catalog, never a mutation or an admission decision, while Core keeps authorization, input limits and deadlines. `DiscoverSelection` resolves a candidate's omitted native values before commit, and `VerifyCredential` verifies a credential's access to owned resources without mutation. Both receive the candidate's `sandbox.DirectConfig` and build any native client for that call only. A node provider accepts only an empty public object, rejects credentials and returns Unsupported for every setup operation and for credential replacement. 4. For a node adapter, export from its package the `BuildLocal` constructor, the typed `native` object it decodes and the native files it adds to the shared node artifacts. Node-local settings, such as host paths, live only in that object; resources are read from the node configuration's `specification`, and the Runtime image comes from the same object. -5. Register its constructor, policies, configuration adapter and operation declaration in `providers/registry.go`. Its key is the provider kind, which also labels the Provider's observations, and checkpoint support reads this entry. The generated projections combine each registered mode, deployment policy and setup declaration with the shared field bounds in `sandbox/deployment_contract.go`: the installer reads the policies from `deploy/node/node_spec.py`, and the TypeScript client and Web read policies and setup declarations from `packages/agents-client/src/deployment-contract.ts`, so Web reads these declarations instead of comparing provider kinds. Regenerate both with `go run ./services/core/cmd/specification-contract -write`. +5. Register its constructor, policies, configuration adapter and operation declaration in `providers/registry.go`. Its key is the provider kind, which also labels the Provider's observations, and checkpoint support reads this entry. The [canonical node specification](../contracts/agents-api/sandbox-deployment.md#canonical-node-specification) describes the generated projections. 6. Supply the distribution artifacts for the adapter and its helper, and offer the provider to operators through the registered configuration contract. -7. Declare the setup wizard's content in a `sandbox.SetupDeclaration` beside `Policy()` and register it as the entry's `Setup`. It carries the label, a summary and cautions in English and Simplified Chinese, whether the provider is the Recommended choice of its mode, the public configuration and credential fields (`Name`, localized `Label`, `Secret`, `Discovered`, `Optional`, `Help`), and presets that fill field values and link the credential console. Registration rejects a secret field unless the credential is required, a discovered field unless `Discovery` is supported, an empty locale and a preset value for an unknown field. Generating the deployment contract rejects more than one Recommended provider in a mode. Web follows the declared mode and fields and names no provider: the declaration reaches it only through the generated `packages/agents-client/src/deployment-contract.ts`, not `deploy/node/node_spec.py`. +7. Declare the setup wizard's content in a `sandbox.SetupDeclaration` beside `Policy()` and register it as the entry's `Setup`. It carries the label, a summary and cautions in English and Simplified Chinese, whether the provider is the Recommended choice of its mode, the public configuration and credential fields (`Name`, localized `Label`, `Secret`, `Discovered`, `Optional`, `Help`), and presets that fill field values and link the credential console. Registration rejects a secret field unless the credential is required, a discovered field unless `Discovery` is supported, an empty locale and a preset value for an unknown field. Generating the deployment contract rejects more than one Recommended provider in a mode. A node configuration, `sandbox.NodeConfig`, holds `provider`, `generation`, `installation_id`, `core_url`, `specification` and the adapter's opaque `native` object. `providers.Build` validates `provider`, `generation`, `installation_id` and `specification`, and passes the configuration with ephemeral `sandbox.LocalOptions` to `BuildLocal`, which decodes `native` strictly. The caller explicitly selects standalone registration or single-provider execution with `Standalone`, or generation-owned execution with a canonical absolute node state directory in `GenerationStateDirectory`. Missing or mixed contexts are rejected. The adapter owns generation-specific native preparation and readiness checks. Microsandbox binds helper leases to the installation, generation and specification digest, then checks the pinned image after platform, capacity and artifact readiness. @@ -258,11 +258,11 @@ A checkpoint-capable generation reports `CheckpointCompatibility`: opaque `Artif `Suspend` may report `SourceStopped` only after publishing a verified full checkpoint durably, stopping the exact source compute and settling source-local capture and cleanup obligations. Core persists the snapshot before marking it suspended; native absence alone is insufficient. `Suspend.ObserveOnly` may finish checkpoint publication and source cleanup for an already verified snapshot of the same operation, but cannot capture another snapshot or stop a source that has resumed running. If a previously published checkpoint is missing or damaged, `ObserveOnly` may return its exact durable ownership receipt with `Status: unknown` and `SourceStopped: false` for cleanup; that receipt proves neither current checkpoint usability nor source absence. `Resume` still verifies the checkpoint before execution. After the source-settlement barrier, source-node unavailability does not prevent restoration. Deletion and ordinary expiry can transfer an idle suspended allocation's cleanup route to an online node in the same artifact domain; deletion does not require execution-class compatibility. The cleanup destination must already serve the same immutable generation and have retained capacity. Without such a node, cleanup stays pending and ownership is not released; operators must restore an eligible node or its capacity. An unknown running or restoring writer never moves merely because its node is unreachable. -Recovery uses `Resume.ObserveOnly` for the persisted target and operation. `RestoreAttemptClosed` is an exact operation ID, not a generic absence flag: the adapter may return it only after durably closing an attempt that never entered native execution and fencing every late request for that ID. An adapter may also close a durably admitted attempt when it can prove that its current invocation failed before dispatching native restoration. The microsandbox adapter does this for the fresh request that created the admission, on any failure before it dispatches native restoration. An expired wire deadline or an unclassified failure closes the attempt, and Core then persists a new operation ID. A snapshot that is missing, fails identity or integrity verification, or fails the adapter's qualification for this generation, such as a resource, workspace or runtime image mismatch, is also closed and reported as `ErrCheckpointUnusable`. That outcome is durable: `Resume.ObserveOnly` for the same operation reports it again, and late requests for the operation ID stay fenced. A Provider never returns it once native restoration was dispatched. On `ErrCheckpointUnusable` Core never restores that snapshot again and [ends its retention at once](#cold-replacement-after-checkpoint-retention). Once native restoration has been dispatched, an unknown outcome retains the same target, ownership and original retention deadline; a missing native listing, helper death or timeout does not authorize replay. Such a restore can remain unavailable until explicit deletion or ordinary retention expiry, and pending inputs can reach their public deadline while waiting. User deletion still requires exact native settlement before resources and ownership can be released. +Recovery uses `Resume.ObserveOnly` for the persisted target and operation. `RestoreAttemptClosed` is an exact operation ID, not a generic absence flag: the adapter may return it only after durably closing an attempt that never entered native execution and fencing every late request for that ID. An adapter may also close a durably admitted attempt when it can prove that its current invocation failed before dispatching native restoration. An expired wire deadline or an unclassified failure closes the attempt, and Core then persists a new operation ID. A snapshot that is missing, fails identity or integrity verification, or fails the adapter's qualification for this generation, such as a resource, workspace or runtime image mismatch, is also closed and reported as `ErrCheckpointUnusable`. That outcome is durable: `Resume.ObserveOnly` for the same operation reports it again, and late requests for the operation ID stay fenced. A Provider never returns it once native restoration was dispatched. On `ErrCheckpointUnusable` Core never restores that snapshot again and [ends its retention at once](#cold-replacement-after-checkpoint-retention). Once native restoration has been dispatched, an unknown outcome retains the same target, ownership and original retention deadline; a missing native listing, helper death or timeout does not authorize replay. Such a restore can remain unavailable until explicit deletion or ordinary retention expiry, and pending inputs can reach their public deadline while waiting. User deletion still requires exact native settlement before resources and ownership can be released. -The E2B adapter checkpoints with the SDK's native pause and `connect`. One sandbox spans every generation of an allocation: `Suspend` pauses it with a memory snapshot, so the daemon survives, and `Resume` reconnects to the same sandbox. The helper's private receipt records the current generation, so a superseded generation is `ErrNotFound`, and `KillCompute` and `DeleteSnapshot` never touch a sandbox that another generation owns. `CheckpointCompatibility` names the endpoint and installation and the template build, and a snapshot that does not match is `ErrCheckpointUnusable` before any native call. E2B keeps no independent workspace filesystem, so retention expiry follows the unqualified branch below. +The E2B adapter's checkpoint behavior is in the [E2B provider guide](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/services/core/tools/e2b-provider/README.md). E2B keeps no independent workspace filesystem, so retention expiry follows the unqualified branch below. -The microsandbox adapter requires an explicit private `checkpoint_root`, outside every guest-accessible filesystem. Its directory is mode `0700`; a private namespace marker identifies the actual checkpoint store. The default installation creates a node-local private store. Cross-node restoration requires operators to mount the same private store on each participating node; mount paths may differ. Each retained checkpoint is an uncompressed native snapshot of the guest's memory and root-disk layers, so a shared store carries that volume for every suspension and cross-node restore. Runtime state directories and workspace bindings are never used to infer this root. The adapter verifies the checkpoint, external filesystem identity and native execution class again before restoration. Restored RAM and file descriptors do not promise continuity of external TCP connections or replay safety for application side effects. +The private `checkpoint_root` is described in the [microsandbox provider guide](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/services/core/tools/microsandbox-provider/README.md). Each retained checkpoint is an uncompressed native snapshot of the guest's memory and root-disk layers, so a shared store carries that volume for every suspension and cross-node restore. Restored RAM and file descriptors do not promise continuity of external TCP connections or replay safety for application side effects. ### Cold replacement after checkpoint retention @@ -296,7 +296,7 @@ The online window and the other node timers are in the [sandbox node protocol](. ## Conformance -E2B template and endpoint validators in the installer and Go adapter consume the shared [selector fixtures](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/services/core/internal/sandbox/e2b/testdata/configuration-selectors.json). Extend these cases with any validation change so both entry points accept the same selectors. +E2B template and endpoint validators in the Go adapter consume the shared [selector fixtures](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/services/core/internal/sandbox/e2b/testdata/configuration-selectors.json). Extend these cases with any validation change. Run `make check-sandbox-provider-contract` while developing. It runs the shared [`contracttest`](https://github.com/MiniMax-AI/OpenAgentCore/tree/main/services/core/internal/sandbox/contracttest) suite through real adapter boundaries with controlled native failures, plus the adapter and node transport tests; `make check` includes the same packages. Call the public failure runner with native-side fixtures instead of a fake `SandboxProvider`, and keep tests for foreign ownership, unknown mutation results, cancellation, no automatic replay, failed cleanup and reference-bound settlement. diff --git a/docs/web/console-api-usage.md b/docs/web/console-api-usage.md index f5c5e0329..077317fb1 100644 --- a/docs/web/console-api-usage.md +++ b/docs/web/console-api-usage.md @@ -27,8 +27,6 @@ Browser requests are same-origin and carry only the console session cookie. The | Issue key | `POST /core/v1/projects/{project_id}/keys` | **Issue key** on an active project, also from Getting started; the plaintext is shown once | | Revoke key | `DELETE /core/v1/projects/{project_id}/keys/{key_id}` | **Revoke**, with a warning when it is the project's last active key | -Names are checked for length (projects 1–128 characters, keys 1–80) and control characters before sending. An issued key's plaintext stays in component memory until the administrator confirms it was saved and is never written to browser storage, URLs or logs. There is no project deletion and no plaintext recovery. - ## Project resources Routes are relative to `/core/v1/projects/{project_id}` and return the same objects as the corresponding public `/v1` operations, so the console applies the public client's strict projections. Archived projects remain readable. @@ -50,7 +48,6 @@ Resource-specific rules: - **Files.** The list is read 100 per page, newest or oldest first. The administrator API has no File content route, so the console offers no download. - **Vaults.** Credential tokens are never returned. The console shows each Credential's name, MCP server URL, authentication type and update time. - **Sessions.** A malformed Session fails the read of its project instead of being skipped. -- **Diagnostics.** The console translates Core's classified reason and never infers a cause from raw logs. The Session page adds the reason's next step, such as checking the model shown on the Session page and the Session's model provider; lists show the reason alone, with the next step in its tooltip. Core returns no Harness or provider message, so an unclassified failure says so. An unavailable or mismatched diagnostic offers an explicit read retry; a retry never replays execution. ## Executor credentials and host connection @@ -61,7 +58,7 @@ The **Executor credentials** section of a Session page appears only when the Ses | List credentials | `GET /core/v1/projects/{project_id}/environments/{environment_id}/executor-credentials` | Read every 5 seconds while visible. The table shows each credential's short `key_id` with its copy button, creation time (`created_at`, which rotation does not change) and status (Active, or Revoked with its time), active first; the credential itself is never listed. The same read's `connection` observation drives the host connection panel: never connected, connected, disconnected, bound credential revoked, and unknown stay distinct, and only a fresh connected read marks the host connected | | Issue or rotate | `POST …/executor-credentials` with `{"key_id", "rotate"}` | Issue keeps the generated key ID before submission. Rotation confirms that the old credential stops working; rotating a revoked credential restores it with a new secret. The private JSON is shown once for download or copy, never stored in browser storage or the query cache, and forgotten on Done | | Revoke | `DELETE …/executor-credentials/{key_id}` | **Revoke**, confirmed (the executor disconnects and does not retry; its daemon stays parked until the operator stops it), then the list is read again and shows the credential as Revoked | -| Installation commands | `GET /core/v1/projects/{project_id}/environments/{environment_id}/installation` | **Connect a host**: Core's short-lived Linux/macOS and PowerShell commands, shown as Core returned them with a platform selector and a link to [Run self-hosted Sessions](../getting-started/self-hosted.md). The commands install the daemon and its Harnesses, start it and check its connection; their authorization expires after 30 minutes, and the console reads them again every 20 minutes. Without an available, unexpired answer the section says the command is unavailable. Archived projects do not read it | +| Installation commands | `GET /core/v1/projects/{project_id}/environments/{environment_id}/installation` | **Connect a host**: Core's short-lived Linux/macOS and PowerShell commands, shown as Core returned them with a platform selector and a link to [Run self-hosted Sessions](../getting-started/self-hosted.md). Archived projects do not read it | In an archived project the section hides **Issue credential** and **Rotate** behind a note and keeps the list and **Revoke**, which Core still allows. @@ -75,12 +72,8 @@ In an archived project the section hides **Issue credential** and **Rotate** beh | Installation | `GET /core/v1/installation` | Settings → Service and deployment installation facts (`public_url`, `api_base_url`, `installation_id`, `source_commit`) and read-only Startup settings (`configuration.settings`; a sensitive setting shows only whether it is `configured`); `api_base_url` in the call samples; `public_url` as the download origin and `--source-url` of the node install and uninstall commands (and the install command's `--core-url`). A sensitive setting with a value, or an unknown member, fails the read | | Core metrics | `GET /core/v1/metrics?range=` | Core metrics page. [Core metrics](../../contracts/agents-api/core-metrics.md) defines every measurement | -`local_only`, or no `public_url`, stops Add node from issuing a command and Clean up the host from giving one. Overview, Nodes and Settings then show a visible warning, with Review the public address leading to Settings → Service and deployment. Nodes disables Add node with a visible reason, and Getting started leaves its sandbox step to do. - Wherever a new key is shown, and without any key on an active project's page, the console gives shell exports of `OPENAI_BASE_URL` (the installation's `api_base_url`) and `OPENAI_API_KEY` (the new key, or a placeholder for a key of the project), with `curl` and Python examples for `GET /v1/agents` and `POST /v1/agents/sessions`, and sends none of them. When the installation is `local_only` it says the API is reachable only on the Core machine, and without an `api_base_url` it says to set `public_url`. -Summary figures are cumulative per Session and are not billing records. Sessions without reported usage count toward coverage but not toward token sums, and the console shows missing values as missing, never as zero. - ## Request log **Request log** reads `GET /core/v1/request-logs` through `AdminClient`. It filters by application or administration surface, exact request ID and request outcome, and refreshes on demand. The list and local detail dialog share one response; opening a row makes no extra request. Read failures preserve the previous response with a visible stale warning. The [request log contract](../../contracts/agents-api/admin-api.md#request-log) owns capture, bounds and response semantics. Explanations live in help tips; missing status or error data is not invented. @@ -118,8 +111,8 @@ An E2B deployment has no nodes; its API key is write-only. Overview and Sandbox ## Writes -- Deletion uses the administrator API with the same preconditions as the public delete operation. Every deletion is confirmed. A 4xx keeps the dialog open with Core's reason, a 404 counts as already deleted, and any other failure is reported as uncertain and followed by a fresh read. -- The console offers Session deletion only for idle or failed Sessions without required actions and never cancels work to make a Session deletable. +- Every deletion is confirmed. A 4xx keeps the dialog open with Core's reason, a 404 counts as already deleted, and any other failure is reported as uncertain and followed by a fresh read. +- The console offers Session deletion only for idle or failed Sessions without required actions. - Project, key, executor credential, deletion and sandbox writes are sent once per explicit action and never retried automatically. An uncertain result stays visible until the administrator reads the state again and decides. - An executor credential issuance with an unknown outcome (no answer, a 30-second timeout, a 5xx) opens an error dialog whose next step is **Refresh list**. If the kept `key_id` is then listed, it was issued and its secret lost: the console offers to rotate it (`rotate: true`) for a fresh secret, shown once. If it is not listed, the next Issue sends the same `key_id` with `rotate: false`; should that return 409 because the first request was issued after all, the console reads the list again and offers the same rotation only if the credential is listed as active in an active project, and otherwise reports the issuance as rejected. A kept `key_id` that is already listed is never sent again, and rotating or revoking it from its row forgets it: the next Issue generates a new `key_id`. @@ -148,7 +141,5 @@ Its help tips and coverage notes state what a request is and what is not counted ## Not consumed -- Any `/v1/**` route, including Session creation, Session events and their stream, message input, function results and cancellation. -- Creation or update of Agents, Environment templates, Skills, Files, Vaults or Credentials, including uploads and Credential token replacement. - Single Turn reads, Artifacts, Session execution configuration, Environment Files and administrative Session archive. - The administrator audit log (`GET /core/v1/audit-log`). Settings shows the installation, each Harness's default model and the sandbox deployment instead. diff --git a/docs/web/console-server.md b/docs/web/console-server.md index 8ecd5beee..758c5a11d 100644 --- a/docs/web/console-server.md +++ b/docs/web/console-server.md @@ -98,10 +98,6 @@ Sign-in errors: 400 for a malformed body, 401 `Invalid Core key`, 405 for a meth With `OAC_WEB_NODE_PAYLOAD_DIR` set, the console serves the matched distribution's node payload at `/node-install/` without sign-in: `node-install.pyz`, `manifest.json`, `SHA256SUMS`, `runtime/seccomp.json`, and the node artifacts the manifest declares under `artifacts/`. An artifact missing locally redirects (307) to its release download. Node install and uninstall commands download from `/node-install/`, so the reverse proxy must send that path to the console. Nodes verify every checksum themselves. -## Public address - -The operator's reverse proxy or hosting platform terminates HTTPS and routes to the console ([Configure the public address](../getting-started/public-address.md)), and `OAC_PUBLIC_URL` records the origin that browsers, applications, nodes and executors use. The console accepts only its host, so DNS rebinding cannot reach it. - ## Verification After installing or changing the console, check: diff --git a/docs/web/index.md b/docs/web/index.md index 186bb186e..ef5e28571 100644 --- a/docs/web/index.md +++ b/docs/web/index.md @@ -54,7 +54,7 @@ Each Harness's default model is set in **Settings → Models and credentials**: The deployment's sandbox backend serves hosted Sessions. An application's `self_hosted` Runtime, including one in its own E2B account, is a separate path that the sandbox configuration does not change. -A loopback public address (`local_only`) keeps nodes and remote applications from reaching Core. The console stays reachable at its own address and [warns about it](./console-api-usage.md#provenance-and-monitoring). +A loopback public address (`local_only`) keeps nodes and remote applications from reaching Core. The console stays reachable at its own address and warns about it. ## Diagnose a failure diff --git a/docs/workspace-provider.md b/docs/workspace-provider.md index 403e78496..8e44df723 100644 --- a/docs/workspace-provider.md +++ b/docs/workspace-provider.md @@ -97,13 +97,13 @@ Workspace storage is retained with its Session until explicit Session deletion. `Bootstrap.Workspace` and `ResumeRequest.Workspace` optionally carry a [workspace filesystem binding](#identity-and-configuration). A Provider whose policy declares workspace storage requires the binding and returns `ErrInvalid` without it; a Provider without that declaration keeps its own Environment disk and rejects a binding. Presence selects the exact external object and never permits a fallback to another disk. Providers validate the tenant and Environment against the binding and its configuration receipt before resolving it. `LocalOptions.Workspace` injects the filesystem resolver; Sandbox Providers never interpret filesystem adapter parameters or native receipts. -`DeploymentPolicy.Workspace` present means the Provider runs every Environment on external storage with the declared attachment requirements; absent means the Provider's own disk and no external storage. The derived immutable `DeploymentSpec.Workspace` receipt records the selected capabilities and is always present for such a Provider; a deployment with no storage selected returns `ErrWorkspaceRequired`, which Core reports as `workspace_storage_required`. The [deployment workflow](../contracts/agents-api/sandbox-deployment.md#resources) never copies filesystem configuration into the generation. Microsandbox requires `host_directory` and `user_xattr`; Docker and E2B reject external attachments. Validation uses the filesystem protocol's combination validator: `EnvironmentDiskMiB` is only a capacity quota request, valid when the storage declares `capacity_quota`, and its default is zero, which requests no quota. Root disk capacity is still required. +`DeploymentPolicy.Workspace` present means the Provider runs every Environment on external storage with the declared attachment requirements; absent means the Provider's own disk and no external storage. The derived immutable `DeploymentSpec.Workspace` receipt records the selected capabilities and is always present for such a Provider; a deployment with no storage selected returns `ErrWorkspaceRequired`, which Core reports as `workspace_storage_required`. The [deployment workflow](../contracts/agents-api/sandbox-deployment.md#resources) owns the resource fields. Microsandbox resolves each Create and Resume binding, including observation of an interrupted restore, and passes only the local directory and immutable ObjectID to its private helper. The helper binds the whole `/environment`; its retained contents and private native history follow the [workspace filesystem scope](#lifetime-and-filesystem-scope) and [Runtime resource directories](configuration.md#runtime-resource-directories). Private HOME, credentials and the compute root are not a durable history store. Object and path labels qualify native resources. Checkpoint restore verifies the snapshot's object, remaps `/environment` through SDK `Volumes`, requires strict external mount policy and rejects every restore warning. Partial restore errors retain the exact target identity for cleanup and never establish readiness. Unknown outcomes remain observations of the original operation. Native Bind checkpoint and guest-local flock continuity do not establish fencing against an unknown writer. ## Kernel NFS adapter -The `nfs` adapter uses an operator-premounted Linux kernel NFS filesystem. The supported setup is NFSv4.2 on trusted AUTH_SYS clients with `root_squash`, using the same export and absolute mount path on Core and nodes. The adapter neither mounts exports nor manages NFS credentials or service accounts. Other operating systems return `ErrUnsupported`. [Configuration](./configuration.md#independent-workspace-storage) owns installation steps and selection. +The `nfs` adapter uses an operator-premounted Linux kernel NFS filesystem. The supported setup is NFSv4.2 on trusted AUTH_SYS clients with `root_squash`, using the same export and absolute mount path on Core and nodes. The adapter neither mounts exports nor manages NFS credentials or service accounts. Other operating systems return `ErrUnsupported`. [Set up workspace storage](./getting-started/workspace-storage.md) owns installation steps and selection. Parameters contain `root`, `namespace_id`, `uid` and, in quota mode, `quota`. `root` is a clean absolute mount path; `namespace_id` is a canonical nonzero UUID; `uid` is the nonzero effective host UID required on both Core and nodes. A Compose installation uses 65532, the UID of Core's container. The operator prepares the mounted root with mode `0700`, owned by this UID, and a regular `.oac-storage-root` file with mode `0600`, owned by the same UID, containing exactly the namespace UUID followed by one newline. Do this on the export's server-side storage as its administrator; root-squashed clients cannot assume privileged ownership changes. The root marker is an assertion of the selected export's identity, not a credential or distributed lock. @@ -134,9 +134,9 @@ stateDiagram-v2 note right of deleted: Create and Resolve refuse the retired identity ``` -NFS storage does not supply compute fencing or automatic distributed failover. Guest `flock` is not a cross-VM writer lock. The single-writer and explicit-deletion requirements above remain mandatory. Confirmed release can permit a fresh VM on another compatible node to attach the same object, but an offline node or an unknown Create, Kill or snapshot-cleanup result never proves that the old writer stopped. Native history and database behavior require qualification of the selected Runtime, Harness and filesystem combination; file persistence alone is not a guarantee of native Session recovery, cross-node memory restore or arbitrary crash durability. +NFS storage does not supply compute fencing or automatic distributed failover. -Core requires the selected Harness profile to support `retained_native_history` when the selected deployment generation uses external workspace storage, which is every microsandbox generation. Creation validates that generation while holding the deployment lock, before committing the Session or compute reservation. Existing Sessions use their immutable workspace binding for Runtime admission and the final execution check. A Provider with its own disk does not require this capability. +Core requires the selected Harness profile to support `retained_native_history` when the selected deployment generation uses external workspace storage, which is every microsandbox generation. Creation validates that generation while holding the deployment lock, before committing the Session or compute reservation. Existing Sessions use their immutable workspace binding for Runtime admission and the final execution check. ## Conformance diff --git a/docs/zh/architecture.md b/docs/zh/architecture.md index 214beac84..78a0388d9 100644 --- a/docs/zh/architecture.md +++ b/docs/zh/architecture.md @@ -1,12 +1,12 @@ --- title: "架构" source: docs/architecture.md -source_hash: 957a59269543af671ccac136b00d54e78da276958c9c765ac43151174c6fd918 +source_hash: 9a52651443a4e985e05a40c1f7c6833fd6f4ecc203ab5fdfbf527566244d9222 --- OpenAgentCore 是 Agent 运行平台,通过可替换的协议实现统一组织 Harness、模型、工具、Session 和执行环境。它将编排、计算资源和原生执行分开。Core 负责 API 和持久状态。沙箱提供商管理计算资源。独立工作区文件系统 adapter 通过单独的协议管理 Environment 持久存储。Runtime daemon 准备 Environment 并运行选定的 Harness;Harness 的原生 SDK 或协议负责模型与工具循环。 -推荐的自部署方案是 microsandbox 配合 kernel NFS 工作区存储。E2B 提供托管计算。Docker 运行在没有检查点或外部存储的节点上。它们使用相同的 Core 编排和能力检查。计算和文件系统 adapter 声明其支持的组合;部署选择不会引入独立的 Agent 执行流程。[沙箱提供商协议](./sandbox-provider.md)和[工作区文件系统协议](./workspace-provider.md)定义其契约和支持的能力。 +[沙箱后端](./getting-started/sandbox-backends.md)页面对比各部署选项。计算和文件系统 adapter 声明其支持的组合;部署选择不会引入独立的 Agent 执行流程。[沙箱提供商协议](./sandbox-provider.md)和[工作区文件系统协议](./workspace-provider.md)定义其契约和支持的能力。 ```mermaid flowchart TB @@ -70,6 +70,6 @@ sequenceDiagram daemon 连接后,Core 检查可用 Harness 和请求的能力。Runtime 准备工作区 Environment 及其能力快照,然后准备或复用 Session Executor。Core 持久化输出、工具交互和回执,供应用读取和接收事件。完成或取消使 Turn 结算;健康的 Executor 可以在同一 Environment 中执行下一个 Turn。 -对于支持检查点的计算资源,Core 自动挂起空闲工作,并在确认快照捕获及源计算停止后释放活跃计算容量。新工作在符合条件的兼容节点上恢复保留的内存;独立工作区存储始终属于同一 Environment 标识。[检查点生命周期](./sandbox-provider.md#suspension)负责空闲准入、唤醒与保留规则,[检查点转移](./sandbox-provider.md#checkpoint-transfer)定义共享存储和兼容性要求。仅凭节点失联不能授权替代写入者。 +[检查点生命周期](./sandbox-provider.md#suspension)负责空闲准入、唤醒与保留规则,[检查点转移](./sandbox-provider.md#checkpoint-transfer)定义共享存储和兼容性要求。 -执行、计算资源和独立工作区存储拥有不同的生命周期。关闭 Executor 本身不会删除存储。[工作区文件系统契约](workspace-provider.md#lifetime-and-filesystem-scope) 负责持久存储保留及显式删除规则;计算快照到期不授权回收工作区存储。准备、连接和执行就绪具有不同状态。[Environment 契约](../../contracts/agents-api/zh/environments.md) 负责准备规则,[Core–Runtime 协议](runtime-protocol.md) 负责顺序、回执和故障处理。 +[工作区文件系统契约](workspace-provider.md#lifetime-and-filesystem-scope) 负责持久存储保留及显式删除规则;计算快照到期不授权回收工作区存储。[Environment 契约](../../contracts/agents-api/zh/environments.md) 负责准备规则,[Core–Runtime 协议](runtime-protocol.md) 负责顺序、回执和故障处理。 diff --git a/docs/zh/concepts.md b/docs/zh/concepts.md index 03d1bce5e..ba320f43a 100644 --- a/docs/zh/concepts.md +++ b/docs/zh/concepts.md @@ -1,7 +1,7 @@ --- title: "概念" source: docs/concepts.md -source_hash: a0749db928e9f8767f5547dec2e6fb4887d1ace214c6fb8a08fe2e7fd5667af0 +source_hash: 61daa0e61271a12b44904a1074f56dbb43721bae5a3e60a03eeaee3d2e5a7b60 --- 项目是 OpenAgentCore 的执行租户。应用使用其 API 密钥;运维人员使用独立的 Core 密钥管理安装实例。[API 命名空间和凭据](api/index.md)将每类调用方映射到对应命名空间和凭据。 @@ -34,4 +34,4 @@ Runtime daemon 在 Linux、macOS 和 Windows 上运行。原生平台行为由 R 凭据值、模型提供商密钥和机密模板数据仅可写入:应用和管理员读取结果都不包含它们。对话文本、Skill 源码和 Artifact 内容是可读取的资源数据,管理员也可以读取。 -公开写入记录执行写入的 API 密钥。管理员写入记录独立的审计身份与目标项目。Web 的 actor 标签仅用于显示;Core 授权依据是 Core 密钥。审计失败会回滚写入。读取不进行审计,审计记录不包含请求体、秘密或文件内容。[写入来源](../../contracts/agents-api/zh/admin-api.md#write-provenance) 和[审计日志](../../contracts/agents-api/zh/admin-api.md#audit-log) 契约定义存储的记录。 +[写入来源](../../contracts/agents-api/zh/admin-api.md#write-provenance) 和[审计日志](../../contracts/agents-api/zh/admin-api.md#audit-log) 契约定义存储的记录。 diff --git a/docs/zh/configuration.md b/docs/zh/configuration.md index c33e626cb..8f3bc3aee 100644 --- a/docs/zh/configuration.md +++ b/docs/zh/configuration.md @@ -1,7 +1,7 @@ --- title: "配置参考" source: docs/configuration.md -source_hash: e9ca5912f98f2acb49373b42e256a7343ea086bb195cccb3d3612085f8aee2de +source_hash: 521923265f147071ec011ab245caf859ad74f23ad9256c88f4899e9c22114d8c --- Core 安装的每项设置、其归属位置以及修改方式。安装程序写入初始值;之后请使用本参考。每项设置都恰好只有一个归属位置,分属以下三类: @@ -138,10 +138,6 @@ Session 创建可以在异步工作区准入之前成功。容量耗尽时,Cor Compose 项目名为 `oac-<10 hex digits>`,服务包括 `init`、`database`、`core` 和 `web`。Core 启动时执行数据库迁移。Web 提供控制台,是唯一发布端口(`OAC_WEB_PORT`)的服务;它转发的内容见[请求边界](./web/console-server.md#request-boundary)。没有服务持有 Docker 套接字,Web 和 Core 也没有 KVM 访问权限。 -## 独立工作区存储 {#independent-workspace-storage} - -microsandbox 要求使用独立工作区存储,唯一支持的组合是与[内核 NFS 适配器](./workspace-provider.md#kernel-nfs-adapter)。在 **设置 → 工作区存储** 中选择它(`/core/v1/workspace-storage`,参阅 [Core 管理 API](../../contracts/agents-api/zh/admin-api.md#workspace-storage));该选择及其租户容量适用于新工作区,现有工作区保留自身配置。[设置工作区存储](getting-started/workspace-storage.md)涵盖 NFS 导出、Compose 挂载、选择负载和配额模式存储服务器。 - ## 附录:没有安装程序时的 Core 环境 {#appendix-core-environment-without-the-installer} Core 读取进程环境。Compose 将 `.env` 插值到环境中,并把机密文件挂载到下表中的容器路径。直接运行 Core 时,将文件变量设为 Core 进程可读取的绝对路径;见[服务指南](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/services/core/README.md)。 diff --git a/docs/zh/getting-started/install-options.md b/docs/zh/getting-started/install-options.md index 5caa5b326..f58bdd291 100644 --- a/docs/zh/getting-started/install-options.md +++ b/docs/zh/getting-started/install-options.md @@ -1,7 +1,7 @@ --- title: "安装选项" source: docs/getting-started/install-options.md -source_hash: e3503e97930dc4d36f34411e5588d4b9c7102b15eeb14d3763a797c18081af2e +source_hash: 55b8023916a824d4d866cb6b1d6c75ccf498791e40975a13d647d9ac547b9a06 --- 本页是安装程序参数的参考,并说明如何用默认方式以外的方式安装:在已有反向代理之后、在托管平台上使用 Docker Compose,或在无法访问互联网的主机上。[默认安装](./install.md)无需任何选项。 @@ -42,7 +42,7 @@ Windows 上 `irm … | iex` 无法传参,默认执行策略也会拦截下载 ## Docker Compose 与托管平台 {#docker-compose-and-hosting-platforms} -在任一[支持的 Core 主机](./install.md#prerequisites)上使用发行版中的 `compose.yaml`。发行流程会固定初始化、Core、Web 和 PostgreSQL 镜像及源码版本。PostgreSQL 16 使用官方镜像,按摘要固定,并与其他镜像一起镜像到 GHCR。它会启动 PostgreSQL、Core 和 Web。数据保存在 Docker 命名卷中。一次性初始化服务会在该卷中生成随机机密信息并准备节点安装程序;Core 启动时执行数据库迁移。[Compose 配置](../configuration.md#compose-installations)负责管理各项设置和数据目录。 +在任一[支持的 Core 主机](./install.md#prerequisites)上使用发行版中的 `compose.yaml`。发行流程会固定初始化、Core、Web 和 PostgreSQL 镜像及源码版本。[Compose 配置](../configuration.md#compose-installations)负责管理各项设置和数据目录。 进行本地试用时,请将发行版的 `compose.yaml` 下载到一个空目录,然后运行: @@ -51,9 +51,7 @@ docker compose up -d --wait --wait-timeout 900 docker compose exec web oac-web core-key ``` -`oac-web core-key` 会将生成的 Core 密钥打印到终端,而不会将其写入容器日志。打开 `http://localhost:8080` 并使用该密钥登录。所有安装机密信息都会自动生成;重启时请保留同一个 Compose 项目及其数据卷。 - -初始化镜像除初始化命令外,仅包含较小的节点安装元数据。首次启动会验证并复制这些元数据,无需下载控制归档或访问 GitHub Releases。后续启动会验证已保存的文件。容器镜像仍需拉取。首次初始化中断后可以重新运行;如果现有数据库缺少安装机密信息,初始化会被拒绝。 +打开 `http://localhost:8080`,使用输出的 Core 密钥登录。 你可以在选择域名前进行部署:将 `OAC_PUBLIC_URL` 保持未设置或留空,然后在平台的域名准备好后,按照 [Compose 配置](../configuration.md#compose-installations)进行设置并重新部署。初始的 localhost 源地址允许服务启动;Web 仅接受已配置的主机,因此重新部署后即可通过平台域名访问。 diff --git a/docs/zh/getting-started/nodes.md b/docs/zh/getting-started/nodes.md index 0863c5a6d..785194012 100644 --- a/docs/zh/getting-started/nodes.md +++ b/docs/zh/getting-started/nodes.md @@ -1,7 +1,7 @@ --- title: "添加和管理节点" source: docs/getting-started/nodes.md -source_hash: 16211a07449e565b8b6ad8856c0060140cd0a18ee16d16300ba969193afede74 +source_hash: 8524f052c003f02834e859bda9c1e1467d7e918f3f3e98b6be726317d078308a --- 节点是一台 Linux 主机,为 Core 托管 Session 运行沙箱。本指南面向在沙箱提供商(参阅[选择沙箱提供商](./sandbox-backends.md))为 microsandbox 或 Docker 时添加、修改和移除节点的运维人员;E2B 不需要节点。 @@ -47,7 +47,6 @@ Core 主机与其他主机一样加入:要在它上面运行沙箱,将它添 从 **节点 → 添加节点** 复制当前命令,其中已填入本安装的地址、校验和与注册令牌。命令先检查 Linux amd64、Python 3.9+ 和必要工具,再下载文件;临时网络故障最多尝试三次。校验失败时,从 Web 复制新命令重试。 - 下载到当前账号的私有 `~/.oac/node-bootstrap` 目录,检查 SHA-256,再通过 `sudo` 执行;root shell 中则直接执行。每次持锁清理下载残片后重试,已验证的 `.pyz` 保留供安装程序使用。 -- 令牌通过标准输入传给安装程序,不出现在进程参数、环境变量或 sudo 日志中。 - 当 `HISTCONTROL` 忽略以空格开头的行时(Debian 和 Ubuntu 默认如此),前导空格能避免命令进入 shell 历史。 安装程序逐阶段展示执行过程,Core 确认节点后展示状态与日志命令摘要。`export NO_COLOR=1` 禁用颜色;命令将其作为 `--no-color` 传过 sudo。程序不输出令牌。 @@ -88,7 +87,6 @@ root 只准备账号、组和服务单元;其他操作(包括 Docker 网络 - 重新执行同一命令是安全的。节点注册后,重新运行使用节点自身凭据,不改变已匹配的内容,也无需令牌。以下情况程序不做任何修改并说明原因:保留的节点身份或节点程序属于其他 Core 地址或安装;或节点注册后沙箱配置发生变化(节点会自行准备,参阅[修改沙箱配置](#change-the-sandbox-configuration)),此时在 **节点** 选择 **移除节点**,在主机上卸载该节点,再通过 **添加节点** 重新添加。 - 节点注册前失败的运行不会在 Core 留下任何记录。下一条命令即使在配置变更之后执行,也会替换上次尝试的配置和不再匹配的已下载文件,保留匹配的文件并继续。 -- 已过期或已在其他主机使用的命令立即失败,显示 `The enrollment command expired or was already used`:生成新命令并在 1 小时内执行。 - 慢速下载期间命令过期时,注册失败并显示 `The enrollment command expired or was already used`。下载文件保留:在 Web 生成新命令后执行,下载从中断处继续。 - 每个文件都会显示进度。失败或中断时保留已收到的部分;重新执行时,每个未完成的文件从中断处继续(`Resuming …`),复用已校验的完整文件,其余照常下载。服务器不支持续传时,该文件从头开始。每分钟下载不足 64 KiB 时会停止,请检查网络后重新执行命令。 - Docker Runtime 镜像约 400 MB,microsandbox 文件约 500 MB。使用 Docker 且网络较慢时,可先加载镜像:将发行资产 `oac--linux-amd64-runtime.tar.gz` 复制到主机并运行 `sudo docker load -i`。安装程序发现精确匹配的镜像后跳过下载。 @@ -108,7 +106,7 @@ root 只准备账号、组和服务单元;其他操作(包括 Docker 网络 - **容量与空闲挂起。** 在 **节点** 中通过 **编辑节点** 修改节点的活跃和保留上限,或用 **使用推荐值** 填入推荐值。在 **设置** → **沙箱与调度** 中,通过 **自动挂起** → **空闲多久后挂起(秒)** → **保存** 修改整个部署的[空闲策略](../../../contracts/agents-api/zh/sandbox-deployment.md#suspension-policy)。 - **沙箱提供商。** 同一页面选择 **重置部署**。自动重置立即归档空闲托管 Session,并让运行中的工作在设定截止时间前完成;强制重置立即取消工作。离线节点必须恢复连接,让 Core 确认清理。重置完成时,Core 已撤销所有节点和未使用命令:配置新的沙箱提供商后重新添加节点。**取消重置** 停止剩余工作;已归档 Session 仍保持归档。 -[重置协议](../../../contracts/agents-api/zh/sandbox-deployment.md#generation-ownership-and-rollout)描述重置会归档和保留哪些内容。归档单个托管 Session 时,使用 [Core API](../../../contracts/agents-api/zh/admin-api.md#session-archive)。 +[重置协议](../../../contracts/agents-api/zh/sandbox-deployment.md#reset)描述重置会归档和保留哪些内容。归档单个托管 Session 时,使用 [Core API](../../../contracts/agents-api/zh/admin-api.md#session-archive)。 ## 跨节点的检查点恢复 {#checkpoint-recovery-across-nodes} diff --git a/docs/zh/getting-started/public-address.md b/docs/zh/getting-started/public-address.md index 755afd06f..e09056b35 100644 --- a/docs/zh/getting-started/public-address.md +++ b/docs/zh/getting-started/public-address.md @@ -1,14 +1,14 @@ --- title: "配置公开地址" source: docs/getting-started/public-address.md -source_hash: 02eb5d34fb53a65e4cadb60ada04c98c5667e083141e56b8c5169683926a8249 +source_hash: 29563e665c6fdcecc63fe7813e7138d100a4dc4f98389625cbdb6fd25d924a21 --- 设置浏览器、节点、沙箱和应用访问 Core 所用的唯一源地址,并在其前面放置带 HTTPS 的反向代理。本指南面向将 Core 暴露到其所在机器之外的运维人员。 ## 选择公开 URL {#choose-the-public-url} -`OAC_PUBLIC_URL` 是应用、节点、沙箱和自托管执行器使用的源地址。Web 只接受其中的主机。Core 从中派生其他地址,列在[`OAC_PUBLIC_URL`](../configuration.md#appendix-core-environment-without-the-installer)下。它是不含路径的 http 或 https 源地址。 +`OAC_PUBLIC_URL` 是应用、节点、沙箱和自托管执行器使用的源地址。Web 只接受其中的主机,因此 DNS 重绑定无法访问它。Core 从中派生其他地址,列在[`OAC_PUBLIC_URL`](../configuration.md#appendix-core-environment-without-the-installer)下。它是不含路径的 http 或 https 源地址。 | 场景 | 公开 URL | | --- | --- | @@ -102,18 +102,10 @@ curl -s -o /dev/null -w '%{http_code}\n' -H 'OpenAI-Beta: agents=v1' https://cor ### 使用快速隧道进行本地试用 {#try-it-locally-with-a-quick-tunnel} -[Cloudflare 快速隧道](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/do-more-with-tunnels/trycloudflare/)会为使用外部入口的试用安装提供一个临时公共 HTTPS 地址。它只会转发到一个端口,因此需要在前面放置一个具有相同路由的本地代理: +[Cloudflare 快速隧道](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/do-more-with-tunnels/trycloudflare/)会为使用外部入口的试用安装提供一个临时公共 HTTPS 地址。 -```caddyfile -http://:8443 { - bind 127.0.0.1 - reverse_proxy 127.0.0.1:8080 -} -``` - -1. 启动代理:`caddy run --config Caddyfile`。 -2. 启动隧道:`cloudflared tunnel --url http://127.0.0.1:8443`。它会打印一个地址,例如 `https://random-words.trycloudflare.com`。 -3. 在 `~/.oac/core/.env` 中将该地址设置为 `OAC_PUBLIC_URL`,并运行 `~/.oac/core/oac apply`。 +1. 启动隧道:`cloudflared tunnel --url http://127.0.0.1:8080`。它会打印一个地址,例如 `https://random-words.trycloudflare.com`。 +2. 在 `~/.oac/core/.env` 中将该地址设置为 `OAC_PUBLIC_URL`,并运行 `~/.oac/core/oac apply`。 每当 `cloudflared` 重启时,该地址都会改变;随后必须重新添加绑定到旧地址的节点。吞吐量较低,因此节点首次下载 Runtime(约 500 MB)时可能很慢;请参阅[慢速链接](./nodes.md#rerun-expiry-and-slow-links)。 diff --git a/docs/zh/getting-started/workspace-storage.md b/docs/zh/getting-started/workspace-storage.md index 511b98aa9..54dc24e86 100644 --- a/docs/zh/getting-started/workspace-storage.md +++ b/docs/zh/getting-started/workspace-storage.md @@ -1,12 +1,12 @@ --- title: "设置工作区存储" source: docs/getting-started/workspace-storage.md -source_hash: 3e1ff9740cc093133658186a8562250f183963f83c657d5413d64058975f75df +source_hash: 153136a405856b60d0bcf749521b33e54fc3f3d4f6bcdaabcfd901fb041e934f --- 将 Session 工作区保存在沙箱节点之外的 NFS 文件系统上,可选择是否设置每个工作区的配额。microsandbox 的每个 Environment 都运行在此存储上,因此请在配置 microsandbox 后端之前先选择它。 -支持的组合是 microsandbox 与[内核 NFS 适配器](../workspace-provider.md#kernel-nfs-adapter)。Docker 和 E2B 使用自有磁盘,不支持独立工作区存储;参阅[选择沙箱提供商](./sandbox-backends.md)。[配置参考](../configuration.md#independent-workspace-storage)列出了该设置所在的位置。 +支持的组合是 microsandbox 与[内核 NFS 适配器](../workspace-provider.md#kernel-nfs-adapter)。Docker 和 E2B 使用自有磁盘,不支持独立工作区存储;参阅[选择沙箱提供商](./sandbox-backends.md)。 ```mermaid flowchart LR diff --git a/docs/zh/maintainers.md b/docs/zh/maintainers.md index 1f363ebc0..8593da678 100644 --- a/docs/zh/maintainers.md +++ b/docs/zh/maintainers.md @@ -1,7 +1,7 @@ --- title: "构建并发布 OpenAgentCore" source: docs/maintainers.md -source_hash: abeea265e04f051163414749fd3e3a9be7b8680d17bec4431038533f9b2d2a3c +source_hash: 0bc83086935af877837d1d791b3ad8eafa486fcc9adbf00b119d4546dc66658c --- 本指南面向负责构建和发布 OpenAgentCore 的维护者。要安装 Core 和 Web,请按 [安装 Core 和 Web](getting-started/install.md) 操作。安装程序代码遵循的规则见 [部署](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/deploy/README.md) 和 [节点安装程序](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/deploy/node/README.md);必需检查见 [CONTRIBUTING](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/CONTRIBUTING.md#required-checks)。 @@ -195,8 +195,6 @@ gh workflow run core-release --repo MiniMax-AI/OpenAgentCore --ref main \ Compose 模板和 Compose 测试发生变更时,会同时选择 `distribution` 固定数据和 `compose` 冒烟作业;Core、Web、共享 Go 软件包和镜像 Dockerfile 的变更也会选择冒烟作业。安装 Docker 后,可在本地运行 `python3 scripts/compose-smoke.py` 重复该测试。该脚本使用唯一的项目、自动分配的回环端口,并将在 `~/.oac/tests/` 下生成构件;退出时移除其容器和数据卷。CI 还会在冒烟步骤失败或中断后执行清理。诊断信息会显示容器状态,但不会打印 HTTP 响应正文或登录密钥。Core、Web 和 ingress 镜像都从当前检出构建,并带有当前检出提交的节点元数据;Web 提供占位页面而不是控制台构建,初始化容器禁用网络运行。冒烟矩阵使用 Linux amd64 和 arm64 原生 runner;原生矩阵在 Linux、macOS 和 Windows 上构建并测试共享的 Core 安装程序。 -`scripts/ci_plan.py` 根据变更路径选择检查组。每个所选集合都包含 `hygiene`,混合变更会累加其使用方,并且每个作业都读取同一计划,而不是维护各自的路径列表。仅修改通知的 PR 会跳过数据库、浏览器和原生作业;同时修改通知和 Core 的 PR 会添加后端和 API 检查。 - | 变更输入 | 所选检查组 | | --- | --- | | `docs/` 和 `contracts/` 下已发布的文档和资源,以及 `docs.json` | `hygiene`、`website` | @@ -221,11 +219,11 @@ Compose 模板和 Compose 测试发生变更时,会同时选择 `distribution` 即使计划或依赖项失败,最终 `check` 仍会运行。它要求计划有效且成功、每个所选作业均成功,并且每个未选择作业均被跳过。失败、取消、作业缺失、意外跳过或意外执行都会导致门禁失败。API/原生可复用工作流是此门禁的直接依赖项。同一 PR 上较新的运行会取消之前的运行。发布检查在请求的不可变 ref 上运行;原生打包在这些检查中执行一次,分发构建会等待其完成。 -要手动执行完整检查,请使用 **Actions → core-check → Run workflow**。遇到暂时性故障时,请使用 GitHub 的 **Re-run failed jobs**,这样已成功的作业可保持完成状态。PR 更新后,Actions 并发机制会取消已被取代的运行。构建和依赖项缓存可加快执行,但不能替代成功的测试。原生发布安装程序通过 Actions 构建产物在同一发布工作流的不同作业之间传递。 +要手动执行完整检查,请使用 **Actions → core-check → Run workflow**。遇到暂时性故障时,请使用 GitHub 的 **Re-run failed jobs**,这样已成功的作业可保持完成状态。原生发布安装程序通过 Actions 构建产物在同一发布工作流的不同作业之间传递。 `CI review and Feishu notification` 工作流仅在 PR 合并到 main 后运行一次。它检出合并后的提交,读取该 PR 已有的检查和日志,通过 `FEISHU_WEBHOOK_URL` 发送一张飞书卡片,概述本次改动、是否符合仓库规则以及实际 CI 状态。它不会触发另一轮测试;审查或发送失败不会让作业失败,也不会阻止合并。关闭未合并 PR 不触发审查。该工作流只针对合并事件使用 `pull_request_target`,绝不在持有通知凭据时检出未合并 PR 的 head。 -浏览器作业各自拥有独立的固定数据和服务;对共享可变固定数据增加 worker 数不安全。失败的浏览器作业保留报告与 trace 七天。原生失败阶段摘要保留七天,详细输出留在 Actions 日志中;凭据和临时安装目录不上传。原生归档仅在显式手动打包或发布时构建并上传,不重新压缩已压缩的归档。发布分发产物保留现有恢复策略;失败发布可以按前述方式复用原构建。 +浏览器作业各自拥有独立的固定数据和服务;对共享可变固定数据增加 worker 数不安全。失败的浏览器作业保留报告与 trace 七天。原生失败阶段摘要保留七天,详细输出留在 Actions 日志中;凭据和临时安装目录不上传。原生归档仅在显式手动打包或发布时构建并上传,不重新压缩已压缩的归档。 本地 Node 复合操作会安装固定版本的 pnpm,并根据锁文件、OS、架构、Node 版本和 pnpm 版本缓存其软件包存储。它缓存下载的软件包,而不是 `node_modules`;安装仍保持冻结状态。Go 分区会保留[发布](#publish-a-version)部分所述的现有模块/编译器缓存。三个原生平台独立运行;并行执行可缩短总耗时,但不会减少机器总时间。 diff --git a/docs/zh/runtime-protocol.md b/docs/zh/runtime-protocol.md index 2f32ea541..211d94737 100644 --- a/docs/zh/runtime-protocol.md +++ b/docs/zh/runtime-protocol.md @@ -1,7 +1,7 @@ --- title: "Core–Runtime 协议" source: docs/runtime-protocol.md -source_hash: d0180316782a49812b3922ed544e2338d66cb735e496c5d08ccac47e1e2b4024 +source_hash: ac10ed0c636be22b92802eeae3af6ded928239c637e24cafe748a62fa16174f0 --- Core–Runtime 协议连接 Core 与 Runtime daemon,定义 daemon 连接上消息的含义和顺序。托管和自托管 Runtime 使用同一协议。 @@ -42,8 +42,8 @@ sequenceDiagram R->>C: ordered envelopes ``` -- [机器连接 API](../../contracts/agents-api/zh/machine-api.md#credentials) 列出凭据类型;项目 API 密钥和 Core 密钥都不是 Runtime 凭据。 -- 两个请求都通过 Bearer header 提供凭据。凭据不得出现在 URL、payload 日志或 trace 中。 +- [机器连接 API](../../contracts/agents-api/zh/machine-api.md#credentials) 列出凭据类型。 +- 凭据不得出现在 URL、payload 日志或 trace 中。 - 立即发送第一个 heartbeat,之后按 bootstrap 返回的间隔发送。每个 heartbeat 声明 `supported_agent_kinds`、其可用性和[能力](#capability-declarations)。第一个 heartbeat 之前能力未知;heartbeat 中缺失的 kind 视为未声明。两者都不允许推断。 - Heartbeat 仅证明存活,不证明执行进度,也不充当此前消息的回执。 @@ -331,7 +331,6 @@ Core 在 Worker 的 Session 调度预约上运行空闲目录读取,活动执 | 2 分钟 | 一次 `runtime_prepare` transfer 的 `begin`–`commit` 暂存阶段 | 传输被拒绝。 | | 64 KiB | `runtime_prepare` transfer 或 `workspace_write` body 的单个 chunk | 更大的 chunk 验证失败。 | | 50 MiB | 一次 `runtime_prepare` transfer 或 `workspace_write` body(私有限制) | 传输在到达 installer 前被拒绝。 | -| 5 MiB | 公开写入的 decoded inline body,由 API 在任何 Runtime 工作前检查 | API 拒绝请求。 | | 4 MiB | 单个入站 transport frame | frame 被拒绝。 | | 8 KiB、128 字节 | `workspace_read` 请求 payload 与 correlation ID,在准入前检查 | 请求被拒绝;过长 ID 不回显,过大的 trace metadata 不进入回复。 | | 1 MiB | `workspace_read` 的原始结果,位于 transport frame 内 | 结果明确截断。 | diff --git a/docs/zh/sandbox-provider.md b/docs/zh/sandbox-provider.md index 940992c5d..45dc17c42 100644 --- a/docs/zh/sandbox-provider.md +++ b/docs/zh/sandbox-provider.md @@ -1,7 +1,7 @@ --- title: "沙箱提供商协议" source: docs/sandbox-provider.md -source_hash: 590c360468ffa40fef877d3ef5cb2b9ee7f213d7e2aeb2ad28df515d965629d4 +source_hash: 9aac833e15cf78b06e4378b7526074bdb4cbac82aef4528e17da382053bc638d --- 沙箱提供商协议连接 Core 与为 Core 管理的(`openai_hosted`)Environment 提供计算资源的 adapter,并由该 adapter 运行启动 Runtime daemon 的有界引导流程。本页面向 adapter 作者:说明提供商必须实现什么,以及可以依赖 Core 做什么。Docker、microsandbox 和 E2B 实现了该协议。 @@ -134,9 +134,9 @@ Checkpoint 支持增加 `Compute` generation、name、ID 和 `SnapshotIdentity` 2. 声明 `sandbox.DeploymentPolicy`。adapter 不再编写 specification 或 resource validator。 3. 基于类型化原生配置实现 `sandbox.ConfigurationAdapter`。`DecodeInput` 严格解析请求中独立的公开 `configuration` 与只写 `credential` 对象。`Encode` 生成白名单公开 selector、只读观测和独立 secret bytes,不透传请求 JSON。`Decode` 恢复已存储 selector 并保留对所属资源的访问,不做远程 admission 或新模板验证。`Normalize` 修改前复制输入。`ResolveChange`、`Equal` 和 `WithCredential` 负责继承、身份与凭据组合。`Requirements` 声明是否需要凭据和公开 Core origin,以及支持哪些 setup 操作:`Discovery` 对应 `DiscoverConfiguration`,`SelectionDiscovery` 对应 `DiscoverSelection`,`CredentialVerification` 对应 `VerifyCredential`。`DiscoverConfiguration` 接收 `ConfigurationDiscoveryQuery{Field, Path}`,其中 `Field` 是已声明的 `Discovered` 字段,`Path` 是前几级已选择的值,并返回 `ConfigurationOptions`:由 `{Value, Label, Resources, Complete}` 组成的列表,`Complete` 表示该选项可保存为字段值,其余选项通向下一级。它验证 query 并返回安全 catalog,不做 mutation 或 admission decision;Core 保留授权、输入限制与 deadline。`DiscoverSelection` 在提交前解析候选项省略的原生值,`VerifyCredential` 验证凭据对所属资源的访问,不修改资源。两者都接收候选项的 `sandbox.DirectConfig`,原生 client 只为该次调用构造。node 提供商仅接受空公开对象,拒绝凭据,对每项 setup 操作和 credential replacement 返回 Unsupported。 4. node adapter 从自己的包中导出 `BuildLocal` constructor、它解码的类型化 `native` 对象,以及它在共享 node artifact 之外添加的原生文件。node-local 设置(如主机路径)只存放在该对象中;resources 从 node 配置的 `specification` 读取,Runtime 镜像来自同一对象。 -5. 在 `providers/registry.go` 中注册 constructor、policy、configuration adapter 和 operation 声明。其键即提供商 kind,也用于标记该提供商的观测;checkpoint 支持读取此项。生成的投影组合每个已注册的部署模式、deployment policy 和 setup 声明与 `sandbox/deployment_contract.go` 中的共享 field bound:installer 从 `deploy/node/node_spec.py` 读取 policy,TypeScript 客户端和 Web 从 `packages/agents-client/src/deployment-contract.ts` 读取 policy 和 setup 声明,因此 Web 读取这些声明,而不比较提供商 kind。通过 `go run ./services/core/cmd/specification-contract -write` 重新生成两者。 +5. 在 `providers/registry.go` 中注册 constructor、policy、configuration adapter 和 operation 声明。其键即提供商 kind,也用于标记该提供商的观测;checkpoint 支持读取此项。生成的投影见[规范节点规格](../../contracts/agents-api/zh/sandbox-deployment.md#canonical-node-specification)。 6. 提供 adapter 和 helper 的发行产物,通过已注册 configuration 契约把该提供商交付给运维人员。 -7. 在 `Policy()` 旁声明 setup 向导的内容:编写 `sandbox.SetupDeclaration`,并注册为条目的 `Setup`。它包含中英文的标签、摘要和注意事项,该提供商是否为其模式的 Recommended 选择,公开配置字段和凭据字段(`Name`、本地化的 `Label`、`Secret`、`Discovered`、`Optional`、`Help`),以及填充字段值并链接凭据控制台的 preset。注册会拒绝:凭据非必需却声明了 secret 字段、不支持 `Discovery` 却声明了 discovered 字段、空的语言文本和指向未知字段的 preset 值。生成部署契约时会拒绝同一模式下多于一个 Recommended 提供商。Web 遵循声明的模式和字段,不出现任何提供商名称:声明只通过生成的 `packages/agents-client/src/deployment-contract.ts` 到达 Web,不进入 `deploy/node/node_spec.py`。 +7. 在 `Policy()` 旁声明 setup 向导的内容:编写 `sandbox.SetupDeclaration`,并注册为条目的 `Setup`。它包含中英文的标签、摘要和注意事项,该提供商是否为其模式的 Recommended 选择,公开配置字段和凭据字段(`Name`、本地化的 `Label`、`Secret`、`Discovered`、`Optional`、`Help`),以及填充字段值并链接凭据控制台的 preset。注册会拒绝:凭据非必需却声明了 secret 字段、不支持 `Discovery` 却声明了 discovered 字段、空的语言文本和指向未知字段的 preset 值。生成部署契约时会拒绝同一模式下多于一个 Recommended 提供商。 node 配置 `sandbox.NodeConfig` 包含 `provider`、`generation`、`installation_id`、`core_url`、`specification`,以及 adapter 的不透明 `native` 对象。`providers.Build` 验证 `provider`、`generation`、`installation_id` 和 `specification`,并将配置与临时 `sandbox.LocalOptions` 传给 `BuildLocal`,由它严格解码 `native`。调用方通过 `Standalone` 明确选择独立注册或单提供商执行,或通过 `GenerationStateDirectory` 中的规范绝对 node state directory 选择 generation 拥有的执行。context 缺失或混合时拒绝。adapter 负责 generation 特有的原生 preparation 和 readiness 检查。Microsandbox 将 helper lease 绑定到安装实例、generation 和 specification digest,然后在平台、容量和 artifact readiness 后检查固定 image。 @@ -260,11 +260,11 @@ Core 在初始化完成、没有 root 或 Subagent Turn 处于排队、进行中 `Suspend` 只有在持久发布已验证的完整检查点、停止精确 source compute,并结清 source 本地 capture 与 cleanup 义务后,才能报告 `SourceStopped`。Core 先持久化快照,再标记 suspended;仅原生资源不存在并不足够。`Suspend.ObserveOnly` 可以为同一 operation 已验证的快照完成检查点发布和源清理,但不能重新捕获快照,也不能停止已经恢复 running 的源。此前已发布的检查点缺失或损坏时,`ObserveOnly` 可以返回其精确持久 ownership receipt,标记 `Status: unknown` 和 `SourceStopped: false`,供清理使用;该 receipt 不证明检查点当前可用,也不证明源不存在。`Resume` 仍在执行前验证检查点。完成源结清屏障后,source node 不可用不再阻止恢复。删除和正常到期可以将空闲 suspended allocation 的清理路由转给同一 artifact domain 内的在线 node;删除不要求 execution class 匹配。清理目标必须已提供同一不可变 generation,且有 retained 容量。没有这样的 node 时,清理保持 pending,不释放所有权;运维需恢复符合条件的 node 或其容量。未知 running 或 restoring writer 不会仅因 node 不可达而迁移。 -恢复用 `Resume.ObserveOnly` 观察已持久化的 target 与 operation。`RestoreAttemptClosed` 是精确 operation ID,不是通用 absence 标志:adapter 只有在持久关闭从未进入原生执行的 attempt,并隔离该 ID 的所有迟到请求后才能返回。如果 adapter 能证明当前调用在派发原生恢复之前失败,也可以关闭已持久 admitted 的 attempt。microsandbox adapter 对创建该 admission 的 fresh 请求,在派发原生恢复之前的任何失败上执行这种关闭。wire deadline 到期或未分类失败会关闭该 attempt,Core 随后持久化新的 operation ID。快照缺失、未通过身份或完整性校验,或未通过 adapter 对当前 generation 的资格检查(例如资源、workspace 或 runtime image 不匹配)时,也会关闭该 attempt,并报告为 `ErrCheckpointUnusable`。该结果是持久的:同一 operation 的 `Resume.ObserveOnly` 会再次报告它,该 operation ID 的迟到请求仍被隔离。一旦已派发原生恢复,提供商不会返回它。收到 `ErrCheckpointUnusable` 后,Core 不再恢复该快照,并[立即结束其保留](#cold-replacement-after-checkpoint-retention)。一旦已派发原生恢复,结果未知的 attempt 保留相同 target、所有权与原始 retention deadline;原生列表缺失、helper 死亡或超时都不授权重放。这种恢复可能一直不可用,直到显式删除或正常保留期到期;等待中的 input 可能达到其公共 deadline。用户删除仍需精确原生 settlement,之后才能释放资源与所有权。 +恢复用 `Resume.ObserveOnly` 观察已持久化的 target 与 operation。`RestoreAttemptClosed` 是精确 operation ID,不是通用 absence 标志:adapter 只有在持久关闭从未进入原生执行的 attempt,并隔离该 ID 的所有迟到请求后才能返回。如果 adapter 能证明当前调用在派发原生恢复之前失败,也可以关闭已持久 admitted 的 attempt。wire deadline 到期或未分类失败会关闭该 attempt,Core 随后持久化新的 operation ID。快照缺失、未通过身份或完整性校验,或未通过 adapter 对当前 generation 的资格检查(例如资源、workspace 或 runtime image 不匹配)时,也会关闭该 attempt,并报告为 `ErrCheckpointUnusable`。该结果是持久的:同一 operation 的 `Resume.ObserveOnly` 会再次报告它,该 operation ID 的迟到请求仍被隔离。一旦已派发原生恢复,提供商不会返回它。收到 `ErrCheckpointUnusable` 后,Core 不再恢复该快照,并[立即结束其保留](#cold-replacement-after-checkpoint-retention)。一旦已派发原生恢复,结果未知的 attempt 保留相同 target、所有权与原始 retention deadline;原生列表缺失、helper 死亡或超时都不授权重放。这种恢复可能一直不可用,直到显式删除或正常保留期到期;等待中的 input 可能达到其公共 deadline。用户删除仍需精确原生 settlement,之后才能释放资源与所有权。 -E2B adapter 使用 SDK 的原生 pause 与 `connect` 做 checkpoint。一个 sandbox 跨越 allocation 的所有 generation:`Suspend` 以内存快照暂停它,daemon 因此存活,`Resume` 重新连接同一个 sandbox。helper 的私有 receipt 记录当前 generation,所以被取代的 generation 返回 `ErrNotFound`,`KillCompute` 和 `DeleteSnapshot` 从不触碰由其他 generation 拥有的 sandbox。`CheckpointCompatibility` 指明 endpoint、安装和模板构建,不匹配的快照在任何原生调用之前即为 `ErrCheckpointUnusable`。E2B 没有独立的 workspace 文件系统,因此保留期到期走下面的未满足条件分支。 +E2B adapter 的 checkpoint 行为见 [E2B provider 指南](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/services/core/tools/e2b-provider/README.md)。E2B 没有独立的 workspace 文件系统,因此保留期到期走下面的未满足条件分支。 -microsandbox adapter 要求显式的私有 `checkpoint_root`,位于所有 guest 可访问文件系统之外。目录权限为 `0700`,私有 namespace marker 标识真实检查点存储。默认安装创建 node 本地私有 store。跨 node 恢复要求运维为参与 node 挂载同一个私有 store,挂载路径可以不同。每个保留的检查点都是 guest 内存与根磁盘层的未压缩原生快照,因此共享 store 要为每次挂起和跨 node 恢复承载这部分数据量。Runtime state 目录与 workspace binding 都不用于推导该 root。adapter 在恢复前再次验证检查点、外部文件系统身份和原生 execution class。恢复 RAM 与文件描述符不保证外部 TCP 连接连续,也不保证应用副作用可安全重放。 +私有 `checkpoint_root` 见 [microsandbox provider 指南](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/services/core/tools/microsandbox-provider/README.md)。每个保留的检查点都是 guest 内存与根磁盘层的未压缩原生快照,因此共享 store 要为每次挂起和跨 node 恢复承载这部分数据量。恢复 RAM 与文件描述符不保证外部 TCP 连接连续,也不保证应用副作用可安全重放。 ### 检查点保留期后的冷替换 {#cold-replacement-after-checkpoint-retention} @@ -298,7 +298,7 @@ Core 在释放 allocation 和 placement 归属前,确认原始创建已结算 ## 一致性测试 {#conformance} -installer 与 Go adapter 的 E2B 模板和 endpoint validator 消费共享 [selector fixture](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/services/core/internal/sandbox/e2b/testdata/configuration-selectors.json)。任何验证修改都扩展这些 case,使两处入口接受相同 selector。 +Go adapter 的 E2B 模板和 endpoint validator 消费共享 [selector fixture](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/services/core/internal/sandbox/e2b/testdata/configuration-selectors.json)。任何验证修改都扩展这些 case。 开发时运行 `make check-sandbox-provider-contract`。它通过真实 adapter 边界和受控原生失败运行共享 [`contracttest`](https://github.com/MiniMax-AI/OpenAgentCore/tree/main/services/core/internal/sandbox/contracttest) 套件,以及 adapter 和 node transport 测试;`make check` 包含相同 package。使用 native-side fixture 调用公开 failure runner,不使用假的 `SandboxProvider`,保留外来 ownership、未知 mutation result、cancellation、不自动 replay、failed cleanup 和 reference-bound settlement 测试。 diff --git a/docs/zh/web/console-api-usage.md b/docs/zh/web/console-api-usage.md index 442f58add..70c296880 100644 --- a/docs/zh/web/console-api-usage.md +++ b/docs/zh/web/console-api-usage.md @@ -1,7 +1,7 @@ --- title: "控制台 API 使用" source: docs/web/console-api-usage.md -source_hash: 4c21f70ece3e6ef7f96cf4b9fda57c14c034196f51c224fcf63174ed0bc88fc1 +source_hash: 4e5430db89b1d39fa0cd3aae2599936f748c6c0059443f7678adce8910b9aa44 --- 本页列出各控制台页面调用的 Core 路由,以及控制台如何限定读取范围。本页面向控制台开发者;运维人员请阅读 [Web 控制台](index.md)。[Core 管理 API](../../../contracts/agents-api/zh/admin-api.md) 定义了路由、响应结构、分页和审计记录;[API 命名空间和凭据](../api/index.md) 定义了本文使用的术语。 @@ -29,8 +29,6 @@ source_hash: 4c21f70ece3e6ef7f96cf4b9fda57c14c034196f51c224fcf63174ed0bc88fc1 | 签发密钥 | `POST /core/v1/projects/{project_id}/keys` | 对活动项目执行 **签发密钥**,也可从 **新手引导** 进入;明文仅显示一次 | | 吊销密钥 | `DELETE /core/v1/projects/{project_id}/keys/{key_id}` | **撤销**;当它是项目最后一个活动密钥时会显示警告 | -发送前会检查名称长度(项目为 1–128 个字符,密钥为 1–80 个字符)和控制字符。已签发密钥的明文会保留在组件内存中,直到管理员确认已保存;绝不会写入浏览器存储、URL 或日志。项目不能删除,明文也无法恢复。 - ## 项目资源 {#project-resources} 以下路由相对于 `/core/v1/projects/{project_id}`,并返回与相应公开 `/v1` 操作相同的对象,因此控制台会应用公开客户端的严格投影。已归档项目仍可读取。 @@ -52,7 +50,6 @@ source_hash: 4c21f70ece3e6ef7f96cf4b9fda57c14c034196f51c224fcf63174ed0bc88fc1 - **文件。** 列表每页读取 100 条,可按最新或最早顺序排列。管理员 API 没有 File 内容路由,因此控制台不提供下载。 - **Vault。** Credential 令牌绝不返回。控制台显示每个 Credential 的名称、MCP 服务器 URL、身份验证类型和更新时间。 - **Session。** 如果某个 Session 格式错误,其所属项目的读取会失败,而不会跳过该 Session。 -- **诊断。** 控制台会翻译 Core 分类的原因,绝不从原始日志推断原因。Session 页面会补充该原因的下一步操作,例如检查 Session 页显示的模型以及该 Session 所用的模型提供商;列表只显示原因,下一步操作放在悬停提示中。Core 不返回 Harness 或模型提供商的原始消息,因此无法分类的失败会如实说明。诊断信息不可用或不匹配时,会提供显式的读取重试;重试绝不会重新执行操作。 ## 执行器凭据和主机连接 {#executor-credentials-and-host-connection} @@ -63,7 +60,7 @@ source_hash: 4c21f70ece3e6ef7f96cf4b9fda57c14c034196f51c224fcf63174ed0bc88fc1 | 列出凭据 | `GET /core/v1/projects/{project_id}/environments/{environment_id}/executor-credentials` | 可见时每 5 秒读取一次。表格显示每个凭据的短 `key_id` 及其复制按钮、创建时间(`created_at`,轮换不会改变该值)和状态(Active,或 Revoked 及其时间),并优先显示活动凭据;绝不会列出凭据本身。同一次读取中的 `connection` 观测值用于驱动主机连接面板:never connected、connected、disconnected、bound credential revoked 和 unknown 保持为不同状态,且只有重新读取并得到 connected 才会将主机标记为已连接 | | 签发或轮换 | `POST …/executor-credentials`,请求体为 `{"key_id", "rotate"}` | Issue 会在提交前保留生成的密钥 ID。轮换前会确认旧凭据将停止工作;轮换已吊销的凭据会恢复该凭据并生成新密钥。私有 JSON 仅显示一次,供下载或复制,绝不会存储在浏览器存储或查询缓存中,并在 Done 时清除 | | 吊销 | `DELETE …/executor-credentials/{key_id}` | **撤销**,需确认(执行器会断开连接且不会重试;其守护进程会保持停止状态,直到操作员将其停止),随后再次读取列表,并显示凭据为 Revoked | -| 安装命令 | `GET /core/v1/projects/{project_id}/environments/{environment_id}/installation` | **连接主机**:按 Core 返回的原始内容显示短效 Linux/macOS 和 PowerShell 命令,并提供平台选择器以及指向 [运行自托管 Session](../getting-started/self-hosted.md) 的链接。这些命令会安装守护进程及其 Harnesses、启动守护进程并检查连接;其授权在 30 分钟后过期,控制台每 20 分钟重新读取一次。如果没有可用且未过期的响应,该区段会说明命令不可用。已归档项目不会读取该信息 | +| 安装命令 | `GET /core/v1/projects/{project_id}/environments/{environment_id}/installation` | **连接主机**:按 Core 返回的原始内容显示短效 Linux/macOS 和 PowerShell 命令,并提供平台选择器以及指向 [运行自托管 Session](../getting-started/self-hosted.md) 的链接。已归档项目不会读取该信息 | 在已归档项目中,该区段会通过提示隐藏 **签发凭据** 和 **轮换**,但保留列表和 **撤销**,因为 Core 仍允许吊销。 @@ -77,12 +74,8 @@ source_hash: 4c21f70ece3e6ef7f96cf4b9fda57c14c034196f51c224fcf63174ed0bc88fc1 | 安装 | `GET /core/v1/installation` | **设置 → 服务与部署** 的 **安装** 信息(`public_url`、`api_base_url`、`installation_id`、`source_commit`)和只读 **启动设置**(`configuration.settings`;敏感设置仅显示其是否为 `configured`);调用示例中的 `api_base_url`;作为下载来源以及节点安装和卸载命令中 `--source-url` 的 `public_url`(还包括安装命令中的 `--core-url`)。如果敏感设置包含值,或存在未知成员,读取会失败 | | Core 指标 | `GET /core/v1/metrics?range=` | **Core 监控** 页面。不存在该路由的 Core(404)会显示为未报告数据。[Core 指标](../../../contracts/agents-api/zh/core-metrics.md) 定义了每项度量 | -如果为 `local_only`,或者没有 `public_url`,**添加节点** 将无法签发命令,**清理主机** 也无法提供命令。随后 **概览**、**节点** 和 **设置** 会显示醒目警告,并通过 **查看公开地址** 前往 **设置 → 服务与部署**。**节点** 会禁用 **添加节点** 并显示明确原因,**新手引导** 则将沙箱步骤保留为待办项。 - 无论是在显示新密钥时,还是在活动项目页面没有显示任何密钥时,控制台都会提供 `OPENAI_BASE_URL`(安装的 `api_base_url`)和 `OPENAI_API_KEY`(新密钥,或项目密钥的占位符)的 shell 导出变量,以及针对 `GET /v1/agents` 和 `POST /v1/agents/sessions` 的 `curl` 和 Python 示例,但不会发送其中任何调用。当安装为 `local_only` 时,控制台会说明 API 只能在 Core 所在计算机上访问;当缺少 `api_base_url` 时,则会提示设置 `public_url`。 -汇总数值按 Session 累计,并不是计费记录。未报告使用量的 Session 会计入覆盖率,但不会计入 token 总量;控制台会将缺失值显示为缺失,绝不会显示为零。 - ## 请求日志 {#request-log} **请求日志** 通过 `AdminClient` 读取 `GET /core/v1/request-logs`,按应用或管理入口、精确请求 ID 和请求结果筛选,并按需刷新。列表和本地详情对话框共用一次响应,打开条目不会发出额外请求。读取失败保留上一次响应并明确提示数据已过期。[请求日志契约](../../../contracts/agents-api/zh/admin-api.md#request-log)定义采集、容量限制及响应语义。说明放在问号提示中,不补造缺失的状态或错误数据。 @@ -120,8 +113,8 @@ E2B 部署没有节点;其 API 密钥为只写。**概览** 和 **沙箱监控 ## 写入 {#writes} -- 删除操作使用管理员 API,并采用与公开删除操作相同的前置条件。每次删除都需要确认。4xx 会让对话框保持打开并显示 Core 给出的原因,404 视为已删除,其他任何失败都会报告为结果不确定,并随后执行一次全新读取。 -- 控制台仅允许删除没有必需操作且处于空闲或失败状态的 Session,并且绝不会为了使 Session 可删除而取消正在执行的工作。 +- 每次删除都需要确认。4xx 会让对话框保持打开并显示 Core 给出的原因,404 视为已删除,其他任何失败都会报告为结果不确定,并随后执行一次全新读取。 +- 控制台仅允许删除没有必需操作且处于空闲或失败状态的 Session。 - 项目、密钥、执行器凭据、删除和 Sandbox 写入操作,每项明确的用户操作只发送一次,绝不会自动重试。结果不确定时,相关信息会保持可见,直到管理员重新读取状态并作出决定。 - 执行器凭据签发结果未知时(无响应、30 秒超时或 5xx),控制台会打开错误对话框,下一步为 **刷新列表**。如果保留的 `key_id` 随后出现在列表中,则说明凭据已签发但其密钥已经丢失;控制台会提议轮换该凭据(`rotate: true`),以生成仅显示一次的新密钥。如果它未出现在列表中,下一次 Issue 会使用相同的 `key_id` 和 `rotate: false`;如果该请求因第一个请求实际上已成功签发而返回 409,控制台会再次读取列表,并且仅当该凭据在活动项目中列为活动状态时才提供相同的轮换操作,否则会将签发报告为已拒绝。已经列出的保留 `key_id` 绝不会被再次发送;从其所在行执行轮换或吊销后,控制台会清除该 ID,下一次 Issue 将生成新的 `key_id`。 @@ -150,7 +143,5 @@ Agent 指标具有以下限制: ## 未使用的接口 {#not-consumed} -- 任何 `/v1/**` 路由,包括 Session 创建、Session 事件及其流、消息输入、函数结果和取消操作。 -- Agent、Environment 模板、Skill、File、Vault 或 Credential 的创建或更新,包括上传和 Credential 令牌替换。 - 单个 Turn 读取、Artifacts、Session 执行配置、Environment Files 和管理端 Session 归档。 - 管理员审计日志(`GET /core/v1/audit-log`)。**设置** 会改为显示安装信息、每个 Harness 的默认模型和 Sandbox 部署。 diff --git a/docs/zh/web/console-server.md b/docs/zh/web/console-server.md index 614b67ca5..33105843d 100644 --- a/docs/zh/web/console-server.md +++ b/docs/zh/web/console-server.md @@ -1,7 +1,7 @@ --- title: "控制台服务器" source: docs/web/console-server.md -source_hash: 927c27a14bbcc394d7218053937eaea8eaf95258cfbe035d48d298568c97caa5 +source_hash: 0c4b0c50abbeabdca19980b4e7a67552680c0cdc7a534c586b58c9fe6a509ca8 --- 控制台服务器(`oac-web`)提供 Web,使用 Core 密钥认证管理员,并将浏览器的 `/core/v1` 请求转发到 Core。本参考面向不使用安装程序运行 Web 的运维人员,以及评审其安全边界的人员。 @@ -100,10 +100,6 @@ flowchart LR 设置 `OAC_WEB_NODE_PAYLOAD_DIR` 后,控制台在 `/node-install/` 无需登录地提供匹配发行版的节点文件:`node-install.pyz`、`manifest.json`、`SHA256SUMS`、`runtime/seccomp.json`,以及清单声明的 `artifacts/` 下节点资产。本地缺失的资产重定向(307)到其发行下载地址。节点安装和卸载命令从 `/node-install/` 下载,因此反向代理必须将该路径发给控制台。节点自行验证每个校验和。 -## 公开地址 {#public-address} - -运维人员的反向代理或托管平台终止 HTTPS 并把流量转到控制台([配置公开地址](../getting-started/public-address.md)),`OAC_PUBLIC_URL` 记录浏览器、应用、节点和执行器使用的源地址。控制台只接受该地址的主机,因此 DNS 重绑定不能访问它。 - ## 验证 {#verification} 安装或修改控制台后,检查: diff --git a/docs/zh/web/index.md b/docs/zh/web/index.md index a1e140659..68ef0758b 100644 --- a/docs/zh/web/index.md +++ b/docs/zh/web/index.md @@ -1,7 +1,7 @@ --- title: "Web 控制台" source: docs/web/index.md -source_hash: eacc43b23569a21ee55d858602846ec93626432c81acba0792a94b0e1f0e1848 +source_hash: b7de8bf7ba82f43f9e60180a6ab544bd7327325cc86055723407c5f21dcd77e9 --- Web 是 OpenAgentCore 安装的管理员控制台:运维人员用 Core 密钥登录,查看健康状态和用量,检查应用创建的内容,并管理项目、密钥、节点和设置。应用使用项目 API 密钥调用 `/v1`,不使用 Web。 @@ -56,7 +56,7 @@ Web 观察并管理安装。它从不启动 Session,也不发送输入;只 部署的沙箱提供商服务托管 Session。应用的 `self_hosted` Runtime(包括它自己 E2B 账号中的 Runtime)是独立路径,修改沙箱配置不会影响它。 -回环公开地址(`local_only`)使节点和远程应用无法访问 Core。控制台仍可通过自己的地址访问,并[展示警告](console-api-usage.md#provenance-and-monitoring)。 +回环公开地址(`local_only`)使节点和远程应用无法访问 Core。控制台仍可通过自己的地址访问,并展示警告。 ## 诊断故障 {#diagnose-a-failure} diff --git a/docs/zh/workspace-provider.md b/docs/zh/workspace-provider.md index a845193f4..aa8e2bcb2 100644 --- a/docs/zh/workspace-provider.md +++ b/docs/zh/workspace-provider.md @@ -1,7 +1,7 @@ --- title: 工作区文件系统协议 source: docs/workspace-provider.md -source_hash: cdc12d0deb1e9c1a7bf90fb2f360b08289a0468a3fb059371eb90055363fc1c6 +source_hash: b7f103947580f0a4b987b3670e78a005668797212edd316f8ed2e7d6bc5d105a --- 工作区文件系统协议连接 Core 和沙箱节点与拥有 Environment 持久存储的适配器。Core 创建和删除存储对象;节点将对象解析为主机目录,由其[沙箱提供商协议](sandbox-provider.md)挂载到计算资源。本文面向文件系统适配器和沙箱提供商的作者。 @@ -98,13 +98,13 @@ Core 在分配任一资源前,选择并验证沙箱提供商与文件系统适 `Bootstrap.Workspace` 和 `ResumeRequest.Workspace` 携带[工作区文件系统绑定](#identity-and-configuration)。策略声明了工作区存储的提供商要求提供绑定,缺失时返回 `ErrInvalid`;未声明的提供商保留自有 Environment 磁盘并拒绝绑定。提供时选择精确的外部对象,不得回退到其他磁盘。提供商在解析前校验租户、Environment、绑定及其配置回执。`LocalOptions.Workspace` 注入文件系统解析器;沙箱提供商不解释文件系统适配器参数或原生回执。 -`DeploymentPolicy.Workspace` 已声明表示提供商在外部存储上运行每个 Environment,并给出挂载要求;未声明表示使用提供商自有磁盘、不使用外部存储。派生的不可变 `DeploymentSpec.Workspace` 回执记录所选能力,此类提供商始终具备该回执;未选择存储的部署返回 `ErrWorkspaceRequired`,Core 将其报告为 `workspace_storage_required`;[部署流程](../../contracts/agents-api/zh/sandbox-deployment.md#resources)不会将文件系统配置复制进代次。Microsandbox 要求 `host_directory` 和 `user_xattr`;Docker 和 E2B 拒绝外部挂载。校验使用文件系统协议的组合校验器:`EnvironmentDiskMiB` 仅是容量配额请求,仅当存储声明 `capacity_quota` 时有效,默认值为零,表示不请求配额。根磁盘容量仍为必需。 +`DeploymentPolicy.Workspace` 已声明表示提供商在外部存储上运行每个 Environment,并给出挂载要求;未声明表示使用提供商自有磁盘、不使用外部存储。派生的不可变 `DeploymentSpec.Workspace` 回执记录所选能力,此类提供商始终具备该回执;未选择存储的部署返回 `ErrWorkspaceRequired`,Core 将其报告为 `workspace_storage_required`。资源字段见[部署流程](../../contracts/agents-api/zh/sandbox-deployment.md#resources)。 Microsandbox 在每次 Create 和 Resume(包括观察中断的恢复)时解析绑定,仅向私有 helper 传递本地目录和不可变 ObjectID。Helper 挂载整个 `/environment`;保留内容与私有原生历史遵循[工作区文件系统范围](#lifetime-and-filesystem-scope)和 [Runtime 资源目录](configuration.md#runtime-resource-directories)。私有 HOME、凭据和计算根磁盘不是持久历史存储。原生资源通过对象和路径标签校验。检查点恢复校验快照对象,通过 SDK `Volumes` 重映射 `/environment`,要求严格外部挂载策略,并拒绝任何恢复警告。恢复部分失败时保留精确目标身份用于清理,不证明就绪。未知结果继续观察原操作。原生 Bind 检查点及 guest 内 flock 连续性不证明对未知 writer 的隔离。 ## 内核 NFS 适配器 {#kernel-nfs-adapter} -`nfs` 适配器使用由运维人员预挂载的 Linux 内核 NFS 文件系统。支持的配置是受信任 AUTH_SYS 客户端上的 NFSv4.2,启用 `root_squash`,Core 和节点使用相同导出和绝对挂载路径。适配器不挂载导出,也不管理 NFS 凭据或服务账户。其他操作系统返回 `ErrUnsupported`。[配置文档](./configuration.md#independent-workspace-storage)负责安装步骤和选择方式。 +`nfs` 适配器使用由运维人员预挂载的 Linux 内核 NFS 文件系统。支持的配置是受信任 AUTH_SYS 客户端上的 NFSv4.2,启用 `root_squash`,Core 和节点使用相同导出和绝对挂载路径。适配器不挂载导出,也不管理 NFS 凭据或服务账户。其他操作系统返回 `ErrUnsupported`。[设置工作区存储](./getting-started/workspace-storage.md)负责安装步骤和选择方式。 参数包含 `root`、`namespace_id`、`uid`,配额模式下还包含 `quota`。`root` 是规范的绝对挂载路径;`namespace_id` 是非零规范 UUID;`uid` 是 Core 和节点均须使用的非零有效主机 UID。Compose 安装使用 65532,即 Core 容器的 UID。运维人员将挂载根目录设置为该 UID 所有、权限 `0700`,并准备同 UID 所有、权限 `0600` 的常规文件 `.oac-storage-root`,内容精确为命名空间 UUID 加一个换行符。应由导出服务端管理员在服务端存储上完成此操作;启用 root squash 的客户端不能假定有权变更所有者。根标记声明所选导出的身份,并非凭据或分布式锁。 @@ -135,9 +135,9 @@ stateDiagram-v2 note right of deleted: Create and Resolve refuse the retired identity ``` -NFS 存储不提供计算 fencing 或自动分布式故障转移。Guest `flock` 不是跨 VM 写入者锁。上述单写入者和显式删除要求仍然必须遵守。确认释放后,可以允许另一兼容 node 上的新 VM 挂载同一对象,但 node 离线或 Create、Kill、快照清理结果未知均不证明旧写入方已停止。原生历史和数据库行为需要对所选 Runtime、Harness、文件系统组合进行资格验证;仅保留文件不保证原生 Session 恢复、跨 node 内存恢复或任意崩溃后的持久性。 +NFS 存储不提供计算 fencing 或自动分布式故障转移。 -所选部署代次使用外部工作区存储(每个 microsandbox 代次都是如此)时,Core 要求所选 Harness profile 支持 `retained_native_history`。创建流程在持有部署锁时校验该代次,然后才提交 Session 或计算预留。既有 Session 的 Runtime 准入和最终执行检查以不可变工作区绑定为依据。使用自有磁盘的提供商不要求此能力。 +所选部署代次使用外部工作区存储(每个 microsandbox 代次都是如此)时,Core 要求所选 Harness profile 支持 `retained_native_history`。创建流程在持有部署锁时校验该代次,然后才提交 Session 或计算预留。既有 Session 的 Runtime 准入和最终执行检查以不可变工作区绑定为依据。 ## 一致性测试 {#conformance} diff --git a/packages/claude-sdk-adapter/README.md b/packages/claude-sdk-adapter/README.md index 155221e2f..61b934f02 100644 --- a/packages/claude-sdk-adapter/README.md +++ b/packages/claude-sdk-adapter/README.md @@ -14,7 +14,7 @@ pnpm --dir packages/claude-sdk-adapter test make check-claude-sdk ``` -The package test compiles TypeScript before running its tests. The Make target also builds and checks the relocatable Runtime artifact. Changes to native execution require real-provider acceptance through Core and Runtime, including continuation and cancellation, followed by the repository's required `make check`. Use [the deployment guide](../../services/core/deploy/claude/README.md) for the qualified environment and Runtime build. +The package test compiles TypeScript before running its tests. The Make target also builds and checks the relocatable Runtime artifact. Use [the deployment guide](../../services/core/deploy/claude/README.md) for the qualified environment and Runtime build. ## Bridge and native lifecycle @@ -82,8 +82,6 @@ Each SDK result supplies one native usage snapshot, including reported failures. Active text uses the SDK's `AsyncIterable` input, with a fresh native UUID mapped to each daemon input ID. A native query may fold text into its current native turn or queue another; one daemon Run can therefore contain several native turns. Never promise Codex's same-native-turn semantics. Writes, queued notifications and user-message echoes do not confirm consumption. Only matching root assistant/partial/result `user_message_uuids` (or the singular fallback) confirm applied input. Typed mid-turn folds may appear only on the native result. Preserve that receipt even when the result reports failure. Check pending functions after the query drains: the SDK may dispatch later-turn callbacks before the earlier result handler finishes. Keep Turn input admission open until every submitted input has a consuming result, even when an earlier result reports an empty native queue. Close admission before releasing final receipt waiters. The outer SDK iterator remains open across Turns. Cancellation resolves unconfirmed pending receipts as unknown and interrupts the exact native Turn. Successful Cancel requires confirmed Turn settlement; process exit alone cannot establish a successful cancellation. Reuse also requires an empty confirmed native queue and settled child work. Otherwise close the Executor. The settled CancellationOutcome retains verified native identity, partial text and observed Usage; a requested resume identity alone is not evidence. Caller wait expiry reports failure/unknown while cleanup retains ownership. Output backpressure cannot turn missing native facts into confirmed settlement. Closed Turn output precedes successful AwaitSettlement; the settled outcome remains readable when connection loss prevents publication. A receipt timeout after a full write preserves the process and pending identity without redelivery; a blocked write is cancelled and released. The private adapter permits one input awaiting consumption and at most 63 extra inputs per Run, preserving the native 64-UUID receipt bound. Durable receipt opt-in separates bounded writes from native consumption waits; calls without it keep the router's ten-second deadline. Larger input capacity and recovery of interrupted input are not supported. Daemon registration alone does not establish public acceptance. -Keep native execution evidence separate from local registration and packaging checks. Live adapter acceptance uses a real provider with private credentials; fixture tests cannot substitute for it. - ## Subagents The adapter uses the pinned Claude Agent SDK's native Agent and SendMessage execution; it implements no model loop. An explicit Runtime request enables the native Agent and SendMessage tools with Claude Code's own agent types and prompts; ordinary requests keep their tools. The packaged `subagent_resources` readiness feature gates this request. diff --git a/packages/mcode-harness/README.md b/packages/mcode-harness/README.md index 8a66d12fd..af4152a39 100644 --- a/packages/mcode-harness/README.md +++ b/packages/mcode-harness/README.md @@ -14,11 +14,7 @@ No second model or scheduling loop is introduced. Hosted public execution is not ## Build -`source.json` pins the CLI source. The pinned npm package supplies native runtime dependencies; the CLI is built from the patched source with its upstream build script and lockfile. On Linux x86_64 or macOS arm64: - -```sh -MCODE_NATIVE_SOURCE=/absolute/upstream/checkout MCODE_CLI_DIR=/absolute/pinned/package bash scripts/build-mcode-harness.sh -``` +`source.json` pins the CLI source. The pinned npm package supplies native runtime dependencies; the CLI is built from the patched source with its upstream build script and lockfile. The commands are in [Runtime images and helpers](../../docs/maintainers.md#runtime-images-and-helpers). `provenance.json` records the hash of every artifact file. The artifact lands in `MCODE_HARNESS_BUILD_DIR`, or a new `${OAC_DEV_HOME:-$HOME/.oac}/build/mcode-harness-` directory. Runtime packaging uses this single artifact and installs it immutably at `/opt/mcode-harness`. diff --git a/services/core/IMPLEMENTATION.md b/services/core/IMPLEMENTATION.md index 79748923a..ea8f437b1 100644 --- a/services/core/IMPLEMENTATION.md +++ b/services/core/IMPLEMENTATION.md @@ -42,13 +42,13 @@ Domain owners, each with its PostgreSQL adapter under `internal/persistence/post ## Request handling -Every Agents API JSON route reads its body through `readJSONObject` before decoding, validation or lookup. The gate requires a JSON Content-Type, applies the route's body limit and rejects invalid UTF-8, malformed JSON (including unpaired surrogate escapes), repeated keys and non-object roots with the official messages; an empty body or `null` becomes `{}`. DELETE, multipart, Core extension and internal routes keep their own readers. Member names match exactly: decode request objects with `decodeInputObject`, or check `inexactMember` before another decoder, so `encoding/json` never matches a case variant. +Every Agents API JSON route reads its body through `readJSONObject` before decoding, validation or lookup. DELETE, multipart, Core extension and internal routes keep their own readers. Member names match exactly: decode request objects with `decodeInputObject`, or check `inexactMember` before another decoder, so `encoding/json` never matches a case variant. -Report a validation failure that has official evidence through the typed field error, which emits `invalid_request_error` with the observed param and message; keep other local codes until their official fields are sampled. Saved and inline Agent configuration pass one path-tracking validator of the pinned shapes before their parsers and harness admission; do not grow it into a JSON Schema engine. A malformed path identifier must produce exactly the response of a well-formed missing one on that route, including for invalid bodies, queries and storage availability: the handler passes it unchanged, the storage call resolves it with `pgunit.PathID` to the never-assigned maximum UUID, and the missing path runs, or reject it directly only where the lookup is the next check. An `after` cursor that does not resolve inside its already resolved parent, malformed ones included, returns that list family's observed error, and foreign and missing cursors stay identical. U+0000 is rejected explicitly only in metadata (`metadata.`), by the `metadata` package; other stored strings rely on the PostgreSQL error mapping, so keep each request's writes in one transaction. +Report a validation failure that has official evidence through the typed field error, which emits `invalid_request_error` with the observed param and message; keep other local codes until their official fields are sampled. Saved and inline Agent configuration pass one path-tracking validator of the pinned shapes before their parsers and harness admission; do not grow it into a JSON Schema engine. A malformed path identifier must produce exactly the response of a well-formed missing one on that route, including for invalid bodies, queries and storage availability: the handler passes it unchanged, the storage call resolves it with `pgunit.PathID` to the never-assigned maximum UUID, and the missing path runs, or reject it directly only where the lookup is the next check. U+0000 is rejected explicitly only in metadata (`metadata.`), by the `metadata` package; other stored strings rely on the PostgreSQL error mapping. List queries reuse the shared parser and error serializer while keeping each family's limit bounds and error fields. The Environment Files list keeps its own path and cursor parsing but follows the same unknown-key and duplicate-key rules, and still rejects malformed query encoding that the shared lists drop. Change page bounds, cursor ownership or parent lookup order only with evidence for that family, and never reproduce an observed upstream server failure as compatibility behavior. -Requests are served on their canonical path and never redirected. `api.CanonicalPaths` wraps the complete server handler in both configurations (the daemon ServeMux and the API router alone), so every route group, middleware, authentication check and handler sees one path. It starts from the request's own spelling, never a path re-escaped from its decoded form: invalid bytes are percent-encoded, unreserved escapes decoded, empty and dot segments resolved with ServeMux semantics and the trailing slash kept, while other escapes such as `%2F` and `%5C` stay encoded; `Path` and `RawPath` stay consistent for chi and the ServeMux. Never route or authorize on a path outside that wrapper. +`api.CanonicalPaths` wraps the complete server handler in both configurations (the daemon ServeMux and the API router alone), so every route group, middleware, authentication check and handler sees one path. It starts from the request's own spelling, never a path re-escaped from its decoded form: invalid bytes are percent-encoded, unreserved escapes decoded, empty and dot segments resolved with ServeMux semantics and the trailing slash kept, while other escapes such as `%2F` and `%5C` stay encoded; `Path` and `RawPath` stay consistent for chi and the ServeMux. Never route or authorize on a path outside that wrapper. On the Beta group, the OpenAI-Beta check (exactly one `agents=v1` value) runs before authentication, and authentication precedes every Beta handler, 404 and 405. The API router's own middleware, not the shared log middleware, sets a fresh `X-Request-Id` (also in the log context), `OpenAI-Version`, `OpenAI-Processing-Ms` and `nosniff`. HEAD runs GET routes, except that streaming, content-download, live directory, Runtime observation and Runtime history routes register an explicit HEAD 405. Every 405 of the API router, unknown methods included, has the JSON body and lists the route's methods in `Allow`. @@ -58,20 +58,19 @@ On the Beta group, the OpenAI-Beta check (exactly one `agents=v1` value) runs be ## Source Files and Artifacts -Source Files are Project resources with a lifecycle independent of copied workspace files. `files` owns their vocabulary, the upload envelope and 512 MiB content bound, the list rules and the create and delete use cases; `filepg` stores immutable source metadata and PostgreSQL large objects in Core's database with the pinned pgx driver. Upload validation, metadata insertion, the bytes and the write audit commit atomically; deletion removes the metadata, unlinks the object and records the audit in one transaction. Keep OIDs private and authorize every metadata, content and delete lookup by tenant before opening a body. Write content through `pgunit`'s large-object writer, which streams bounded chunks and reports the size and SHA-256 digest; never hold an entire upload in memory or use a filename as a filesystem path. A direct download of the `user_data` purpose is rejected after the tenant-scoped metadata lookup, while workspace copies keep their authorized `filepg` read and Session creation copies an initial file's object inside its own transaction through `filepg.ReadSourceForCopy`, which locks the source and bounds the read. A read-only repeatable-read transaction preserves an admitted source across concurrent deletion; resolve that snapshot before entering the Environment write path, and a later deletion never undoes a completed workspace copy. Bound request and transaction lifetimes, roll back incomplete bodies and never retry an ambiguous commit automatically. Backups must include PostgreSQL large objects. +Source Files are Project resources with a lifecycle independent of copied workspace files. `files` owns their vocabulary, the upload envelope and 512 MiB content bound, the list rules and the create and delete use cases; `filepg` stores immutable source metadata and PostgreSQL large objects in Core's database with the pinned pgx driver. Upload validation, metadata insertion, the bytes and the write audit commit atomically; deletion removes the metadata, unlinks the object and records the audit in one transaction. Keep OIDs private and authorize every metadata, content and delete lookup by tenant before opening a body. Write content through `pgunit`'s large-object writer, which streams bounded chunks and reports the size and SHA-256 digest; never hold an entire upload in memory or use a filename as a filesystem path. A direct download of the `user_data` purpose is rejected after the tenant-scoped metadata lookup, while workspace copies keep their authorized `filepg` read and Session creation copies an initial file's object inside its own transaction through `filepg.ReadSourceForCopy`, which locks the source and bounds the read. A read-only repeatable-read transaction preserves an admitted source across concurrent deletion; resolve that snapshot before entering the Environment write path, and a later deletion never undoes a completed workspace copy. Bound request and transaction lifetimes, roll back incomplete bodies and never retry an ambiguous commit automatically. -Session Artifacts are immutable published copies, separate from live workspace files and source Files. The private output exporter reuses the authorized workspace path boundary and streams bounded bytes; publication requires complete capture and confirmed helper and transport success, not merely valid archive syntax. The daemon owns and drains the exporter's stdout pipe separately from child reaping, so pull-transport backpressure cannot consume the process-exit I/O deadline; after helper exit, each pipe read has one second, reset after consumer delays, which rejects inherited pipes that never close. Cancellation closes the owned reader and the dispatch consumer, then waits for the child. Never extract an output archive into Core's filesystem or hold the execution lease through a large transfer. +Session Artifacts are immutable published copies, separate from live workspace files and source Files. The private output exporter reuses the authorized workspace path boundary and streams bounded bytes. Never extract an output archive into Core's filesystem or hold the execution lease through a large transfer. -Capture bytes into private large objects without a Session admission lock. Before capture, seal native input under that lock with the private capture marker; later messages reuse the Environment input reservation and wait for the next Turn, and the public Turn stays in progress until publication settles. Directory reads during capture use an independent read-only preparation, not the released native Run. `sessions.StageTurnArtifacts` stages a confirmed export as a pooled use case, never on the execution lease: it authorizes the Environment before reading the export, admits only distinct regular files under `outputs/` within the capture limits followed by nothing but zero padding, and after the transfer takes the Session lock through `sessionpg.LockSession` and rechecks that the Turn is in progress without a cancellation request. `sessionpg` streams each file into a large object in that one transaction, so a rejected or failed export rolls back its content. The metadata is published in the same transaction as the Turn's completion. `sessions.EndTurn` decides Artifact publication and `sessionpg` applies it in the Turn's terminal transaction, not the capture transaction: capture commits and releases the Session lock before the Turn completes, and the terminal transaction holds the Session lock that also orders Artifact deletion. Drop staged rows whose SHA-256 equals the newest remaining published Artifact for the path, ordered by the producing Turn's creation time and then ID (publication time can come from the Runtime and does not order Turns), and unlink their large objects in the same transaction. Published rows are never modified. Failed and cancelled Turns discard private objects, and Session deletion removes private and published copies. The exporter skips output symlinks by their `lstat` type without following them; hard links, other special files, device crossings and concurrent changes reject the capture. `sessionpg` serves stored reads authorized independently of Environment availability, so published Artifacts outlive the Environment, and reads content from one snapshot, so a read admitted before a deletion finishes. Deletion takes the Session lock, unlinks the content and records the write audit in one transaction. +Capture bytes into private large objects without a Session admission lock. Before capture, seal native input under that lock with the private capture marker; later messages reuse the Environment input reservation and wait for the next Turn, and the public Turn stays in progress until publication settles. Directory reads during capture use an independent read-only preparation, not the released native Run. `sessions.StageTurnArtifacts` stages a confirmed export as a pooled use case, never on the execution lease: it authorizes the Environment before reading the export, admits only distinct regular files under `outputs/` within the capture limits followed by nothing but zero padding, and after the transfer takes the Session lock through `sessionpg.LockSession` and rechecks that the Turn is in progress without a cancellation request. `sessionpg` streams each file into a large object in that one transaction, so a rejected or failed export rolls back its content. The metadata is published in the same transaction as the Turn's completion. `sessions.EndTurn` decides Artifact publication and `sessionpg` applies it in the Turn's terminal transaction, not the capture transaction: capture commits and releases the Session lock before the Turn completes, and the terminal transaction holds the Session lock that also orders Artifact deletion. Drop staged rows whose SHA-256 equals the newest remaining published Artifact for the path, ordered by the producing Turn's creation time and then ID (publication time can come from the Runtime and does not order Turns), and unlink their large objects in the same transaction. The exporter skips output symlinks by their `lstat` type without following them; hard links, other special files, device crossings and concurrent changes reject the capture. `sessionpg` serves stored reads authorized independently of Environment availability, so published Artifacts outlive the Environment, and reads content from one snapshot, so a read admitted before a deletion finishes. Deletion takes the Session lock, unlinks the content and records the write audit in one transaction. - A malformed `environment_id` Artifact filter resolves to the never-assigned maximum UUID, so it matches nothing without a text comparison. ## Environment files and Skills -- Environment file list tokens bind a digest of the tenant, Environment, requested directory, effective order and limit, plus a fingerprint of the full sorted regular-file path and size list and an offset that is a multiple of the limit. Every page rereads the directory; there is no cursor registry, cache or snapshot. Reject every token mismatch with the single official token message. -- The directory helper checks each requested path component with `Root.Lstat` below `os.OpenRoot(workspace)` and opens the final directory with `O_NOFOLLOW`. A missing component, a regular file or a symbolic link maps to the distinct `not_directory` result, which the daemon and gateway carry only for directory reads; Core turns it into an empty page. `not_found` (Claude SDK adapter reader), permission, transport and uncertain results keep their errors. +- Environment file list tokens bind a digest of the tenant, Environment, requested directory, effective order and limit, plus a fingerprint of the full sorted regular-file path and size list and an offset that is a multiple of the limit. +- The directory helper checks each requested path component with `Root.Lstat` below `os.OpenRoot(workspace)` and opens the final directory with `O_NOFOLLOW`. A missing component, a regular file or a symbolic link maps to the distinct `not_directory` result, which the daemon and gateway carry only for directory reads; Core turns it into an empty page. `not_found`, permission, transport and uncertain results keep their errors. - A Files.create write intent stores a digest of the path, size and content, not a path ledger, so Core cannot tell a file an earlier Files.create wrote from any other file; an existing regular file therefore gets the untracked-file message. Reserve the intent under the Session lock before dispatch. The daemon verifies the complete body's SHA-256 before calling the writer, and the writer creates parents with `Root.MkdirAll(0700)`, writes `.oac-write-` in the workspace root and publishes it with `Root.Link`, which never replaces an existing entry. Known refusals return `write_rejected` with `reason` `destination_directory` or `unsafe_destination`; Core settles the intent as `rejected`, which leaves no committed receipt and releases the mutation owner. Only an exact committed or rejected receipt settles an intent; nothing settles an unknown one automatically. -- Check the 5 MiB inline bound after path validation and Base64 decoding, and before the pending-hosted check, the source File lookup and execution. The JSON body limit still admits the Base64 form of 50 MiB so that oversized inline bodies up to that size get the official message. - `skillpg` serializes Skill version uploads, default changes and version deletion on the owning Skill row lock, and `skills` decides a version deletion over the locked Skill. Deleting the default version deletes the Skill only when no other version row exists, through the same cascade as Skill deletion, so every encrypted version row goes in the same commit. `next_version` only increases. ## Scheduling, preparation and pending input @@ -135,11 +134,10 @@ Provider input validation uses the adapter rules in `internal/harnessconfig`: on [Vaults and credentials](../../contracts/agents-api/vaults.md) describes the resources, selection rules, refresh and deletion. `vaults` implements them, with `vaultpg` as its storage, under these rules: - Credentials are children of tenant-owned Vaults. Creation admits the owner in the same SQL statement as the insert; retrieval joins the owning Vault; listing enforces Project and Vault ownership on the parent, cursor and row query. Metadata queries never select ciphertext and need no encryption key. -- `vaultpg` seals secret values before they reach SQL, with Core's separately configured random 32-byte key and the standard library's random-nonce AES-GCM. The versioned authenticated binding covers tenant, Vault, Credential, authentication purpose and exact destination. Never reuse daemon transport encryption for this storage. A missing, unreadable or malformed key fails startup. +- `vaultpg` seals secret values before they reach SQL, with Core's separately configured random 32-byte key and the standard library's random-nonce AES-GCM. Never reuse daemon transport encryption for this storage. - A static replacement is one SQL mutation scoped by tenant, Vault, Credential, auth type and destination, reusing the safe metadata for the immutable binding; it never decrypts the previous token, and a failed write keeps the old row. - OAuth refresh and replacement serialize on the Credential row lock, authenticate the stored grant metadata against its encrypted copy before using an endpoint, and persist the refreshed grant before returning an access token, so a stale refresh cannot undo a deletion or Vault cascade. - Credential deletion is one mutation checked by tenant, Vault and ID; Vault deletion removes the parent and its Credentials through the foreign-key cascade in one SQL statement, without decrypting, needing the key or calling providers. -- At dispatch, Core rechecks the tenant, attached Vault, selected ID, frozen auth type and exact URL before scoped decryption, and the token enters only the transient daemon request. A missing key or binding failure never falls back to anonymous execution. - `credentialcrypto` ciphertext is a format version byte followed by the standard AEAD nonce, ciphertext and tag. The authenticated data holds a fixed domain and version plus the binding (tenant, Vault, Credential, auth type, exact destination). Keep the domain string unchanged: existing rows must still decrypt. - Random-nonce GCM allows at most 2^32 encryptions per key. `secrets/core/credential.key` also seals model providers, the E2B key, Skills, initial files and environment setup, so every sealed write counts toward that bound; there is no rotation or re-encryption path. @@ -164,7 +162,7 @@ Core replaces a Turn's usage with each complete valid token breakdown, which `se Subagent observations use the common types in `internal/agentdaemon/proto/subagents.go`. `sessions` projects them and `sessionpg` assigns their public IDs, under the Session lock of the leased execution journal; native names, history parsing and outcome proof stay in the adapters. Child Turns have their own table, separate from Core's queue, and Session Turn reads and the Session event stream carry root work only. Do not reintroduce mixed Session Turn pages. Repeated effects are idempotent, root output freezes first, and child Items are delivered before their terminal Turn snapshot. -Function calls are stored per tenant, Session and Turn with immutable public and executor call identities and arguments. Result admission and application receipts serialize on the Session lock with cancellation and terminal transitions. `sessions.AdmitFunctionResult` keeps the complete caller-validated result, including omitted versus null error and output and ordered text and image parts; identical retries return the saved decision and changed results conflict. Recording a call through `sessions.ExecutionOperations` moves the Turn to `waiting`, and the last application receipt resumes it. A function result may join a message or cancel batch: its explicit Turn and call identity select an existing call, admission never creates a Turn for it, and the result and its input retry record commit in one transaction. Targets resolve only after the tenant Session lookup; an unknown call or a call of another Turn is 400 `invalid_request_error`. The input cursor skips function results, whose own receipts decide application. A Session with functions waits for a device that declares `function_tools`, and Core delivers each saved result once per live dispatch, appending a non-null error as a final text part because the native result has no error field. +Function calls are stored per tenant, Session and Turn with immutable public and executor call identities and arguments. Result admission and application receipts serialize on the Session lock with cancellation and terminal transitions. `sessions.AdmitFunctionResult` keeps the complete caller-validated result, including omitted versus null error and output and ordered text and image parts; identical retries return the saved decision and changed results conflict. Recording a call through `sessions.ExecutionOperations` moves the Turn to `waiting`, and the last application receipt resumes it. A function result may join a message or cancel batch: its explicit Turn and call identity select an existing call, admission never creates a Turn for it, and the result and its input retry record commit in one transaction. The input cursor skips function results, whose own receipts decide application. A Session with functions waits for a device that declares `function_tools`, and Core delivers each saved result once per live dispatch. Neutral tool and message observations go into the journal before `sessions.ProjectSource` projects public Items; the Item projector in `items` validates the shared observation contract and never decodes engine-native snapshots. `command_output` fragments update Items only for a command already indexed in the same Turn, and commit with their `agent.output.command_execution_output.delta` events. Completion output replaces accumulated drafts, terminal Items ignore late fragments, and cancellation keeps partial output without inventing a successful completion. @@ -180,7 +178,7 @@ A streaming Session creation reuses atomic input admission and the live event lo Public Items read a projection updated in the same Session transaction as admitted messages and journal batches. Item IDs derive from the Turn and source identity, and the first-observation timestamp and tie breakers never change when content or status does. Each new Item's Session position is allocated under the Session lock, preserving observation order for equal timestamps, and each Turn allocates its own zero-based `output_index`, which inputs do not consume; updates and retries keep both. -Item merging never mutates the incoming observation or the previous snapshot: public text delta events read the original fragment after merging, while the Item keeps the accumulated text, and the content slice is copied before its text pointer is replaced. A first observation without its own fragment carries its unchanged text in one delta. Stored Item payloads use the wire encoding, explicit nulls included, so a replayed child Item compares equal to its stored payload. Structured tool JSON is kept without float conversion, and an unfinished call never becomes a successful result. When a Turn ends, `sessions.EndTurn` makes its unfinished Items incomplete with their partial content and reports them in Session position order before the Turn's event and its settled Session activity; `sessionpg` gives them one shared settlement time. Function results are Session input Items: they emit `item.added` with a null `output_index` and never `item.done`, whose upstream union allows only agent output, and their public output and error come from the saved submission. +Item merging never mutates the incoming observation or the previous snapshot: public text delta events read the original fragment after merging, while the Item keeps the accumulated text, and the content slice is copied before its text pointer is replaced. A first observation without its own fragment carries its unchanged text in one delta. Stored Item payloads use the wire encoding, explicit nulls included, so a replayed child Item compares equal to its stored payload. Structured tool JSON is kept without float conversion, and an unfinished call never becomes a successful result. When a Turn ends, `sessions.EndTurn` makes its unfinished Items incomplete with their partial content and reports them in Session position order before the Turn's event and its settled Session activity; `sessionpg` gives them one shared settlement time. ## Worker ownership @@ -212,9 +210,9 @@ The check between Turns changes nothing and never revives a cleanup request. Ter ### Per-node lifecycle workers -Each registered node has one serial lifecycle worker that owns its gate, allocation and pending cursors, connections and wake hints; E2B allocations share one serial lifecycle without a node. A thin coordinator discovers nodes and shuts workers down, and never holds its map mutex during database, provider or wait operations. Workers advance independently, so a stuck provider on one online node never stalls another: lifecycle concurrency is one operation per node and grows with the node count. Offline workers stay, so their retained resources remain observable after reconnection. +Each registered node has one serial lifecycle worker that owns its gate, allocation and pending cursors, connections and wake hints; E2B allocations share one serial lifecycle without a node. A thin coordinator discovers nodes and shuts workers down, and never holds its map mutex during database, provider or wait operations. Offline workers stay, so their retained resources remain observable after reconnection. -Allocation scans filter by node before their 32-row page limit, and pending scans join the unreleased committed placement. Each node advances its own cursor, including past failed observations, and wraps once at the end. Direct provisioning resolves the tenant-scoped placement before entering that node's gate, and an active allocation must agree with it. Only confirmed release permits a new placement; a disconnect never moves an active allocation. +Allocation scans filter by node before their 32-row page limit, and pending scans join the unreleased committed placement. Each node advances its own cursor, including past failed observations, and wraps once at the end. Direct provisioning resolves the tenant-scoped placement before entering that node's gate, and an active allocation must agree with it. Before releasing the execution lease, the coordinator stops accepting work and cancels and drains every node worker and direct caller. Lease loss affects everything; ordinary provider failures stay within their node. A planned deployment drain or node retirement cancels lifecycle contexts synchronously between leased operations, through the lease gate with its five-second bound, including an active manual reconcile, and never cancels an in-flight leased query just to change configuration. A failed cancellation fence closes manager admission and reports owner failure. A failed retirement keeps the original lifecycle identity and gate until owner shutdown, and the drain barrier stays closed. Session locks, deployment capacity transactions and revision-checked receipts stay authoritative, and no external operation holds a database lock. @@ -228,7 +226,7 @@ The idle clock starts no earlier than the allocation entering the running comput The Session-locked capacity-pressure decision rechecks pending work, wake requests and activity, then takes the shared deployment capacity lock and confirms compatible waiting demand. Pressure-triggered reclamation is conservative: an in-flight reclamation delays another only when its node is currently eligible to serve the same demand. Unavailable or incompatible nodes keep their ownership receipts without blocking healthy capacity. -The Worker lease, the Session lock and the per-node gates own suspension for every provider. New Turn claims, file-write intents and capture admission serialize under the Session lock and share one compute-phase check; new pending work cancels a capture and wakes the same source. Normal preparation waits for the compute phase to be running, after the authenticated resume handshake, and pending input stays pending when its promotion conflicts with a lifecycle transition. Compute phases and revision-checked receipts live on the allocation. A consumed snapshot never rolls a running generation back. +The Worker lease, the Session lock and the per-node gates own suspension for every provider. New Turn claims, file-write intents and capture admission serialize under the Session lock and share one compute-phase check; new pending work cancels a capture and wakes the same source. Normal preparation waits for the compute phase to be running, after the authenticated resume handshake, and pending input stays pending when its promotion conflicts with a lifecycle transition. Compute phases and revision-checked receipts live on the allocation. Live file requests wait for the current Runtime's credential-authorized connection and Harness declaration before entering the file work queue; a completed compute `Create` alone is not Runtime readiness. Planned suspension uses an Environment and suspension token on the daemon connection. A transient disconnect before confirmation retries the same armed suspension with bounded attempts and backoff; a permanent authentication or protocol rejection closes it. @@ -236,14 +234,13 @@ Live file requests wait for the current Runtime's credential-authorized connecti A [reset](../../contracts/agents-api/sandbox-deployment.md#reset) is durable execution state that the runtime manager advances outside its counted work. Start, escalation, cancellation, setup, update and finalization serialize through the mutation gate. Idleness is rechecked with the Session lock and then the deployment lock, never in the reverse order during finalization. Work is keyset-paged and bound to the reset's request time and generation, with the absolute deadline and validated audit provenance persisted. -One snapshot and timestamp partition the held resources. Offline ownership comes from the allocation's or active placement's node, with the same [online](../../contracts/agents-api/node-generation-protocol.md#connection) predicate as presence, independently of provider readiness; no cleanup failure, offline state or empty read authorizes a synthetic release. At zero held resources, Core drains outside database transactions, rechecks under the deployment lock and clears the deployment in one transaction, then publishes a generation-bearing empty provider without fallible work. If the final write or drain fails, it restores the committed provider with a bounded owner context before releasing the mutation gate; if that recovery fails, admission stays fenced and the owner stops. +At zero held resources, Core drains outside database transactions, rechecks under the deployment lock and clears the deployment in one transaction, then publishes a generation-bearing empty provider without fallible work. If the final write or drain fails, it restores the committed provider with a bounded owner context before releasing the mutation gate; if that recovery fails, admission stays fenced and the owner stops. An administrator [Session archive](../../contracts/agents-api/admin-api.md#session-archive) keeps its Project scope and hosted eligibility checks in a Session-first transaction, together with Environment expiry, cancellation, Runtime authority revocation and audit; a reset's background archive reconstructs the actual Project scope from trusted records and keeps the requester's provenance. The ordinary provider lifecycle releases compute and snapshots. Archive cancellation keeps a healthy receipt path until the terminal commit: only the archive that first revokes a device records its exact `archive_cancel_turn_id` (ordinary revocation clears it, and repeated cleanup keeps it), and the existing authenticated delivery drains that cancellation within the bound that [Reset](../../contracts/agents-api/sandbox-deployment.md#reset) states. Core tracks the delivery through `done`, the cancellation acknowledgement and the terminal commit, independently of subscription removal, and grants no new connection, input, file or MCP authority or lease renewal. No transaction or lifecycle gate waits for the receipt, and a lost peer, expiry or restart falls back to ordinary failure and cleanup, never a fabricated cancelled outcome. ### Specification, credentials and observations - Each allocation's immutable specification and the current credential resolve in one `deploymentpg` snapshot, with no stale-credential cache, no fallback to the current specification and no unloading of a provider map that retained allocations need. -- Credential replacement fences provider calls and waits for helper subprocesses to exit, even after cancellation, before verifying again and committing; the audit entry never carries secret fields. - Observations of offline, missing or unconfirmed resources persist only bounded, sanitized codes, separately from the lifecycle, and a host restart never fabricates a running observation. - Runtime observation of a node allocation goes through its immutable placement as one bounded read-only Provider operation; an absent capability or transport returns unavailable, never a Core-local fallback. @@ -251,6 +248,6 @@ An administrator [Session archive](../../contracts/agents-api/admin-api.md#sessi - Core error details are scoped by the `/core/v1` router's writer mark, never by a request path test. Write Core errors with `writeCoreError` and typed `CoreErrorDetails` values (string, number, null and string-array constructors); invalid or empty details are omitted as a whole. The mark preserves error observation, flushing and `http.ResponseController` access. A shared handler or a Core-looking path alone never changes a public or machine error envelope. Core authentication runs before operation configuration checks, and unknown paths keep their status and admission rules. When adding a code with details, document its fixed keys in `contracts/agents-api/core-errors.md`, and pass only safe Core-owned facts: never submitted values, secrets, native text or provider bodies. - Operation validators keep their original error text, sentinel identity and validation precedence. Package-owned typed errors carry fixed field metadata; only the marked Core error mapper translates it into operation codes and safe bound or catalog details. Sandbox validation metadata travels through `deployment.ConfigurationError` without changing transaction or provider authority. Public Session provider validation stays byte-for-byte unchanged; cover it with handler-level golden responses. The Core clients ignore malformed optional details and never retry a write. -- Core metrics instrument the existing worker and job owners without changing scheduling, lease or retention behavior. Count `execution_unavailable` at the HTTP error writer, once per rejected response; never capture request or response bodies and never infer the count from other 503s or failed Turns. Process CPU, RSS and cgroup limits are sampled by the 30-second Core metrics loop into the same bounded in-memory ring; the first CPU interval and restart gaps stay null, and host usage never substitutes for process usage. Root Turn history is queried read-only from PostgreSQL with native timestamps. Builds inject the source commit with `-ldflags` into `main.buildRevision`. Keep the response shape aligned with `packages/agents-client/src/core-metrics.ts`. +- Core metrics instrument the existing worker and job owners without changing scheduling, lease or retention behavior. Count `execution_unavailable` at the HTTP error writer, once per rejected response; never capture request or response bodies and never infer the count from other 503s or failed Turns. Root Turn history is queried read-only from PostgreSQL with native timestamps. Builds inject the source commit with `-ldflags` into `main.buildRevision`. Keep the response shape aligned with `packages/agents-client/src/core-metrics.ts`. - Public resource writes carry the authenticated key's provenance separately from the execution principal. Record the operation and any creation ownership with `auditpg.RecordWriteAudit` in the business transaction, never in response middleware or an asynchronous queue; a failed record rolls back the write. Internal lifecycle and refresh work never acquires public provenance, and retries never replace ownership. Environment uploads persist the safe request origin before dispatch and record success with the confirmed Runtime receipt, not the native filesystem call. Never put payloads, paths or secrets in audit metadata. Do not confuse key identity with the Session creator identity used for retries. - Administrator writes reuse the public resource deletion and serialization code and record their administrator audit entry with `auditpg.RecordAdminMutation`, or `RecordDeploymentMutation` for a deployment-wide write, in the same transaction. Administrator provenance takes precedence over a key's. diff --git a/services/core/deploy/mcode/README.md b/services/core/deploy/mcode/README.md index 8f6e9120b..82a9073b9 100644 --- a/services/core/deploy/mcode/README.md +++ b/services/core/deploy/mcode/README.md @@ -23,7 +23,7 @@ Each Session has its own native data directory, `$OAC_RUNTIME_HOME/runtime/mcode | Text (`environment: none`) | A private working directory inside the Session's data directory | No native file, process or fetch tools, web search, `mcode-tools`, thread goals or user questions. The native task controls (`task_query`, `task_output`, `task_stop`) are always present; with Subagents enabled, the default agent also gets delegation. Permission mode `bypassPermissions`, so MCP tools run without approval prompts | | Workspace | The Environment's workspace directory, for the native process, the ACP Session and the native tools | The native `read`, `write`, `edit`, `bash`, `grep`, `glob`, `todowrite` and `web_fetch` tools, permission mode `bypassPermissions`, the selected workspace Skills and the frozen tool environment in the native process environment | -The text profile rejects workspace tools; it accepts service-origin HTTP MCP servers, which the adapter passes in the ACP `mcpServers` of the native Session. In both profiles public functions run as native MCP tools: the daemon serves them from a loopback HTTP MCP server named `functions` that exists for the Session, and passes it in `mcpServers` with a per-Session bearer token. Native fails a call after 120 s ([difference](../../../../contracts/agents-api/index.md#differences-from-openai)). Functions and `multi_agent` cannot be combined. Workspace execution requires Linux or macOS ([`workspace.go`](../../../../apps/daemon/internal/agent/mcode/workspace.go)). +The text profile rejects workspace tools; it accepts service-origin HTTP MCP servers, which the adapter passes in the ACP `mcpServers` of the native Session. In both profiles public functions run as native MCP tools: the daemon serves them from a loopback HTTP MCP server named `functions` that exists for the Session, and passes it in `mcpServers` with a per-Session bearer token. Native fails a call after 120 s ([difference](../../../../contracts/agents-api/index.md#differences-from-openai)). Workspace execution requires Linux or macOS ([`workspace.go`](../../../../apps/daemon/internal/agent/mcode/workspace.go)). In the workspace profile, the frozen installation's Skills are linked into the Session's `skills` directory under their exact names and loaded through the native `skill` tool. Native discovery also reads the workspace's `.minimax`, `.claude` and `.agents` Skills, as a local run does. Public MCP servers use the Environment origin on a workspace and the service origin on the text profile, as the [MCP origin contract](../../../../contracts/agents-api/environments.md#public-mcp-connection-origin) describes. With Subagents enabled, child agents use the same native tools. In both profiles the adapter turns off what needs a MiniMax account or outlives a Turn or asks the user: `askUser`, `beta.mcodeTools`, `beta.threadGoal`, `features.mavis` (its tool carries cron, session send, agent create and MCP create) and `features.webSearch`, and it leaves `website_deploy` out of `tools`. `web_search: disabled` also removes `web_fetch`. Background Bash is off: the patched CLI gates it on `OAC_RUNTIME_MCODE_BACKGROUND_BASH=off`, and the adapter rejects a CLI that does not report it off. diff --git a/services/core/tools/e2b-provider/README.md b/services/core/tools/e2b-provider/README.md index 053ae426e..2ff65c7eb 100644 --- a/services/core/tools/e2b-provider/README.md +++ b/services/core/tools/e2b-provider/README.md @@ -19,7 +19,7 @@ The account key is stored encrypted in Core's database and is write-only. It rea | `command` | `RunCommand` | Runs one bounded command as the Runtime user on a running sandbox whose bootstrap completed; output is limited to 1 MiB per stream | | `compute`, `renew_compute` | `GetCompute`, `RenewCompute` | One sandbox spans every generation of an allocation, and the receipt records the current generation, the pause and the resume. Any other generation is `not_found`. `renew_compute` extends a running sandbox's lease and otherwise observes like `compute` | | `suspend` | `Suspend` | Pauses the sandbox with the SDK's native memory snapshot, so the daemon survives and the paused sandbox is the snapshot. A pause that is dispatched but not seen complete is `unconfirmed`, never running | -| `resume` | `Resume` | Records the attempt as dispatched, then reconnects with `connect`; the same operation is never dispatched twice and a failure after dispatch is `unconfirmed`. A snapshot that does not match the paused sandbox, its endpoint or its template build is `checkpoint_unusable` before any native call, durably for the operation | +| `resume` | `Resume` | Records the attempt as dispatched, then reconnects with `connect`; the same operation is never dispatched twice and a failure after dispatch is `unconfirmed`. A snapshot that does not match the paused sandbox, its endpoint and installation, or its template build is `checkpoint_unusable` before any native call, durably for the operation | | `resume_compute` | `ResumeCompute` | Thaws the source only while it is running | | `kill_compute`, `delete_snapshot` | `KillCompute`, `DeleteSnapshot` | Destroy the sandbox only for the generation that owns it; a superseded generation owns nothing | | `command_compute` | `RunCommandCompute` | Runs one bounded command, such as the wake command, on the running current sandbox | @@ -42,7 +42,7 @@ The request and credentials arrive on standard input; standard output carries on The state directory is the fixed `/state/e2b` ([private Provider state](../../../../docs/configuration.md#compose-installations)), which Core creates when it loads the Provider. It must be owned by the helper's user and grant no group or other access, and it lives on durable private storage across Core restarts. Its receipts hold SDK connection material, ownership identities and one-shot creation claims; they are not Core execution state. Losing the directory cannot authorize recreation or successful cleanup; never delete receipts after an uncertain call. -A helper holds its allocation's lock until the SDK operation returns, even after Core's caller times out. Core tracks the helper's actual exit, and a credential change waits for running helpers without killing them. Helper exit never proves that a remote Create settled. Core serializes lifecycle requests per allocation and never replays Create. +A helper holds its allocation's lock until the SDK operation returns, even after Core's caller times out. ## Create @@ -58,7 +58,7 @@ An unknown Create is never repeated. A Create whose connection material was lost Inspection uses SDK metadata and ID reads only. SDK `connect` is used only in Resume, because it resumes paused compute. The version-pinned constructor that restores a client from saved connection material is confined to [`sdk.py`](sdk.py) and covered by a no-connect, no-create test. Resource drift fails inspection but still permits ownership-based cleanup. -`CreateSettled` proves that the original Create and bootstrap can no longer mutate; it is independent of `BootstrapComplete`. An empty lookup never settles an unknown Create. Kill destroys every matching sandbox, confirms that none remains and only then records a settled tombstone; it returns `State=absent` with `CreateSettled`. A settled rejected Create with no sandbox IDs proves absence without a cloud request, so GetInfo and Kill still succeed when the key is invalid. Ordinary missing compute has no such proof. +Kill destroys every matching sandbox, confirms that none remains and only then records a settled tombstone; it returns `State=absent` with `CreateSettled`. A settled rejected Create with no sandbox IDs proves absence without a cloud request, so GetInfo and Kill still succeed when the key is invalid. Ordinary missing compute has no such proof. ## Observations @@ -66,7 +66,7 @@ Inspection uses SDK metadata and ID reads only. SDK `connect` is used only in Re ## Credential verification -`verify_credential` checks the fixed build, the key's paginated team-owned template listing and each settled live receipt against the labelled sandbox listing. Template and sandbox scans each stop at 100 pages or the 30-second deadline. A missing or unsettled receipt, a repeated template cursor or an unconfirmed read never authorizes replacement, and no receipt changes. A readable public template does not prove that the key owns it. Core scans retained generations and allocation pages under one bounded verification context, then repeats the verification while provider calls are fenced, before it commits the new key. Authentication rejection, ownership mismatch and uncertainty return distinct fixed codes. +`verify_credential` checks the fixed build, the key's paginated team-owned template listing and each settled live receipt against the labelled sandbox listing. Template and sandbox scans each stop at 100 pages or the 30-second deadline. A missing or unsettled receipt, a repeated template cursor or an unconfirmed read never authorizes replacement, and no receipt changes. A readable public template does not prove that the key owns it. Authentication rejection, ownership mismatch and uncertainty return distinct fixed codes. ## Build and tests diff --git a/services/core/tools/microsandbox-provider/README.md b/services/core/tools/microsandbox-provider/README.md index ac6bac559..c47bba0ac 100644 --- a/services/core/tools/microsandbox-provider/README.md +++ b/services/core/tools/microsandbox-provider/README.md @@ -1,6 +1,6 @@ # microsandbox Sandbox Provider helper -microsandbox runs each hosted Session in its own microVM on a Linux amd64 node with KVM. Core forwards provider operations to the node over the [node protocol](../../../../contracts/agents-api/node-generation-protocol.md); the node's adapter ([`sandbox/microsandbox`](../../internal/sandbox/microsandbox)) runs this helper once per operation. The private helper wire version is 7; mismatches are rejected. The helper links the microsandbox Go SDK v0.7.8 with its FFI library, so Core and the node program stay CGO-free Go binaries. It implements the checkpoint operations of the provider-neutral `sandbox.SandboxProvider` with full snapshots. It has no daemon, lifecycle database, scheduler or network control plane. +microsandbox runs each hosted Session in its own microVM on a Linux amd64 node with KVM. Core forwards provider operations to the node over the [node protocol](../../../../contracts/agents-api/node-generation-protocol.md); the node's adapter ([`sandbox/microsandbox`](../../internal/sandbox/microsandbox)) runs this helper once per operation. The helper links the microsandbox Go SDK v0.7.8 with its FFI library, so Core and the node program stay CGO-free Go binaries. It implements the checkpoint operations of the provider-neutral `sandbox.SandboxProvider` with full snapshots. It has no daemon, lifecycle database, scheduler or network control plane. [Sandbox Provider protocol](../../../../docs/sandbox-provider.md) owns the provider contract. [Sandbox deployment](../../../../contracts/agents-api/sandbox-deployment.md) owns the resources and suspension policy; the [nodes guide](../../../../docs/getting-started/nodes.md) owns node installation, host requirements, the node's directories and its network policy. @@ -38,9 +38,9 @@ Core persists operation IDs, source and target generations, exact identities and - **ResumeCompute** thaws the same resident source after an aborted suspension. The pinned SDK handle method is name-based; the allocation lock and ID checks before and after the call fence every managed replacement. Manual lifecycle changes in the managed namespace are unsupported. - **DeleteSnapshot** accepts only the derived operation selector and matching artifact identity. It fences delayed publication and restoration with a durable deletion receipt, then removes everything else in the operation directory without opening the snapshot, so damaged content or a staging directory left by an interrupted capture never blocks owned cleanup. An unsettled admitted restore blocks deletion. The small ownership tombstone remains; Core owns retention and cleanup order. -After a lost response Core uses `ObserveOnly`. It never starts a capture or restore. For an existing verified capture it may complete the receipt and exact paused-source cleanup, but never captures again or kills a running source. Source termination is recorded before local removal so a lost cleanup response can be settled from the receipt. A valid ownership receipt remains discoverable when the snapshot is missing or corrupt: observation returns its snapshot identity with unknown source state and `SourceStopped=false`, so ordinary explicit cleanup can proceed. Restore still requires full snapshot verification. An interrupted restore may finish the derived proof on the exact running target, but never restarts a stopped target or restores again. +Source termination is recorded before local removal so a lost cleanup response can be settled from the receipt. An interrupted restore may finish the derived proof on the exact running target, but never restarts a stopped target or restores again. -Before native restore, the helper synchronizes an admission record bound to the request operation, snapshot and target. Observation of a never-admitted operation writes a permanent closed record and returns that exact `RestoreAttemptClosed`; late dispatch is rejected. A fresh dispatch checks its existing request deadline after admission and immediately before calling native Restore. If the deadline has elapsed, it durably closes its exact attempt while still holding the allocation lock and returns without invoking Restore. Native FFI waits remain uncancelled. Any other failure of that fresh dispatch before native Restore also closes its attempt. A missing snapshot, a failed receipt, identity or integrity check, or a failed resource, workspace or runtime-image qualification also marks the record unusable: every later request for the operation returns `checkpoint_unusable`, and Core [ends the checkpoint's retention](../../../../docs/sandbox-provider.md#cold-replacement-after-checkpoint-retention). This proof is never inferred from a previous admission or a native Restore error. After admission, helper death or native absence alone never establishes no effect. Observation checks an admitted target before reading the snapshot; typed absence or a non-running target remains unconfirmed without verifying it. A running target still requires full snapshot verification. Successful resume requires an observable exact running target. An uncertain admitted restore retains its artifact and owner until explicit deletion or the original retention deadline; the normal cleanup operation uses the native lifecycle fence and records completion. The helper itself never shortens retention or substitutes a cold restart. +Before native restore, the helper synchronizes an admission record bound to the request operation, snapshot and target. Observation of a never-admitted operation writes a permanent closed record and returns that exact `RestoreAttemptClosed`; late dispatch is rejected. A fresh dispatch checks its existing request deadline after admission and immediately before calling native Restore. If the deadline has elapsed, it durably closes its exact attempt while still holding the allocation lock and returns without invoking Restore. Native FFI waits remain uncancelled. Any other failure of that fresh dispatch before native Restore also closes its attempt. This proof is never inferred from a previous admission or a native Restore error. After admission, helper death or native absence alone never establishes no effect. Observation checks an admitted target before reading the snapshot; typed absence or a non-running target remains unconfirmed without verifying it. A running target still requires full snapshot verification. Successful resume requires an observable exact running target. An uncertain admitted restore retains its artifact and owner until explicit deletion or the original retention deadline; the normal cleanup operation uses the native lifecycle fence and records completion. The helper itself never shortens retention or substitutes a cold restart. GetCompute, commands, cleanup and the next suspension verify restored provenance from the persisted VM configuration after the consumed artifact is deleted. diff --git a/website/README.md b/website/README.md index b8e982057..02a7952e0 100644 --- a/website/README.md +++ b/website/README.md @@ -16,7 +16,7 @@ pnpm --dir website preview # serve the build at http://127.0.0.1:4181 - The content root is the repository root. `docs/**` and `contracts/**` keep their paths as URLs, so `docs/architecture.md` is `/docs/architecture`. `website/index.md` and `website/zh/index.md` are mapped onto `/` and `/zh/`. Chinese sources in `docs/zh/` and `contracts/agents-api/zh/` map to `/zh/docs/` and `/zh/contracts/agents-api/`; the language switch opens the matching page. - `docs.json` owns the page list and order: three tabs (Docs, API, Extend), each with groups of pages. The top nav shows one item per tab, and each page's sidebar shows only its own tab. `/llms.txt` lists every page under `## › ` with a one-sentence summary taken from the page's first paragraph. Each page's frontmatter `title` is its label. Every Markdown page under `docs/` and `contracts/` is built, and a test fails when one is missing from `docs.json`. -- Each page's source Markdown is published next to its HTML (`/docs/architecture.md`), and the `README` paths declared in `docs.json` redirect to their section index. +- Each page's source Markdown is published next to its HTML (`/docs/architecture.md`). - Relative links from a page to a file that is not a published page, such as `CONTRIBUTING.md` or `openapi.yaml`, are rewritten to GitHub at build time, so the Markdown works unchanged on GitHub and on the site. The visual system combines warm paper surfaces, green accents and ruled sections. Landing headings use self-hosted Space Grotesk (loaded by the landing only); body text uses Inter and code uses Geist Mono. Chinese text uses the system Chinese sans-serif stack. Both light and dark themes share the same hierarchy.