",
- "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 1375e9c80c4c6536c91719723979a7246d1fb942..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 78978
zcmcG$by$_#+C5Bncb9axfOMyTgmib8bR!_$-Q7xqbT`u7B`qZ#>!;#&yZ2_FbKdW|
zzU4o|se+OIgkuDT6oA=OjJ5M_i
zawlLFasEi$OM$?fa1=2ACepLc4cRmI#^hnu9mjLgbIVQ1tjjs#di%m_Dq^p-l2Gq~
z<%aKq;Q+AZ)#qh)pMTMNqMh>4eP4B#aNYWa@YZX__IBk9;ilJx
zTiTude!&u7B_I~yazA((_=VwsZ-cMSD;Qw>KzPwQ5BS7q4iLECxVyYNIwou))M@^3#zuy4NY-0j~b
zJP@8`>;WvlT@r8){a||U_P}>Vcm}xR`}FOd{mZOxZ_fY#F@S~)0AS0V&T|QH4=4iI
zQ1d3|Pss*jSnz-=m=$!xvJ?0g)hMj{ryvR_&KI2s4hLoyTPj{<8Fh{-u@}3`Rq5_f
zrzr7Ks+NC6mnqIbqn&kyNu+62==8R)?`c}yybbDda;9l|36P|Q_zxfX@ArHq=5mjv
zfi9(B3kOuz1LV0%!3|`k>{($bUdpViqx66LihsXfSrUD_736q}2m>x04ntJs@_4Tnau72p{PI+9(%oIE}-3)m#soV)(Su9d^&`
zdY!B~PpZC>_@SY8d#dICZ$CB>`|$IK@~7lDBz`-x|G2JyJ@ZOTxcq;K0Y)(DlN%8F
zSTIdWs0KM?hfI7nJ;3~M4vc$&{I)RZRmngLXB|n8+MSzHacP#>OJv1Vt}WIXhOUGv
zcc*Q^w9*3fs&NiHie-D{fFf@Scc|O2ZExLM5e>`ehf^Kgg*qF9zH1s<*QP!Zu*Orf
z6Rq&@`GLd(LnkhqqRICr#4M|GhZEcW+JiUTujH!qFgt_cPbyBEYQ64YH#$0F75(;M
z{vP9RJ+~RDA0znNb_9Ux`tcY4h*^_W`a#<3qQ!9!+HGOFye|r%^D@MnkYB?*6%2*=
zh%-IDtJl*hBUH(zZRn2zXS(@~aiOW>3kze)h84(#N%$MEJ1N2ow&B)(b=^Sw6FTeW=;v
zTLK0rwQ$?kLo&iaOGT3utrHG!Y+nbdlm^IfURf%dz%YI0?5R7s3FL~C_Vj`%6mYhHg5Bd(I+2dux?%z|Haa;JvU@+I5_GCj=7j?^g
z-`;NUprk;!HkHk8WT7oHVo=da!_h^hr7|1{sa=fZQ^RzE!P%e@nt~}26R!UjJlo}o
zwF6!SiuHVDguE05$wH=Rj~0?$_uJOhk`%|5xUWduKoJ;f^xCYtp8eMo?1-#QgX%sP
z*0fe9?E;DxHC%>BgH9u<@=T1W-;N+Y=GEzE^1x3CzTIqdHG+&f>^J`n2BEM@9lE
zW_Dw}Ky)OQ8KY+VlH*y6w8(~tYUo*S)wbb4I%VzDr<_qLm0Xs1-;^?KO1*uFWM>Xg
z$`=G2*wZhfvJVU%(We&PAE$XX>Q@c}A7iQedA?JXPyI~9qWV0amUo#WI3T@Ah=HTB
z@@gkn)=?r5FSL8*77ATUu#526SN>zsGjbKpY{Y}o?-|gM;+9GHxe)Sw>FY`=w$V$Z
z=+$*`s^Oe0K}evFSf4)nZd1C-rdXjFM6Pm^m_Q^qc)h0=lP`2N)IL`m`}-MCr?M@H
zBX8^HRGVM(X`SC72nSl_e1SxA?+QqGA&HLr>G82L20CssZD%>TAeq4x6`qD=-O
zVr|H7`W*%m?kGjGKn8SZLjSJm7mQ+NtOJN_TbN_L*n<=RV`ZAqkH
zKt9=f(1$(=$rdSF2@1g3hgmi#uNg~tl87ZU2lDVb!Jd`HH_ERsbm6b?_u9!CRLR}E
z9M!vkiJRY16jC%$^RWV?s%chyX*~y;ye@b$mzQT2K|*%5dZSUQQ16_Ig!xzp{
zIBISaek=iEc=s&4;iEX!R2DUK#IMb%x-GtjLw>6nGVH$l!z2(2zxq+8P0SPh=|2>^
z`&2ri`L=<-bUyr(4S|_v8*A6q<@5IDeLuGuh|BlzM;f@x_0RuLZ!@q@^oA5aAgEkk
z(~Al@tT0!U_;X%C426RE(t4r%zx?Q|6!jVfq*T_{|y-Yalu!!
zrDj`Se!+}+fBtW%;g28w@32E=%l4vpZviNKo2V;Qt^hX#TJl3uE_ZN0D3@s4WvR&i
zXL}kTVzbz#3u7|5wIlPGM2R^lUc9$Lw}12}gm%+e44eo+>cG%e=L+AG%TJLGQ9
zc)RotommmN$Q&N-2;20?q_vT`aG;u7H!BI)@z?ZIrnk*Od@5W52?oLXm-Qu|sIqZz
zPjz*j7`qB_zctI`e?zZIc3?vEVoU&(?phMv#D?I{D>DBYbLs*&fuZGJLl8_+$AaI=
za5-^c&1`6RE{vvinw
z3rP#gb`gVmN?SF7VTMFRb4Be4(S_n$*UTeV8a)<_=&w{tWY-1>Yh7%kevUYmxby7*
zpBOh>DRimH16DQ-lxdcNM@QqS;Sh3J0*G|rhKie(5kIVTJusREb}iScCpGfd2eCHJ
zbilOR4-({^L;~kB(GR%j0o5h%*XL?Yo;QZ6KtTjhR$IWTvKmKYbauFY+c0gVr>7d(
zfng*Vm3ztvLT?SAS569nY9SSQAv`3;y;kJ&wjWOO^XG;3$Knrmql>`UU>XO3{B_5O
z{oGO;%PHx;yqAPh%SWKUpM`^zUiU2yUyR3nK{DSs6c?L^Lv1VR^Arfzwwa)l46bMVM?gB7gi
z!a~sUm#T38{Plhn7;-3!fd4Y!{VF;`bF(MWPX(lZ3c__v0aZxw6Wao8YV-LE+R+6g
zs^FE9J+#9>x$=s=quv2k971>;R)e}2ZCqT%JJd+AZq}?}feYgEuXkU&<%~rA+7;((
zv%Y1|w`@^F@ESddt2hu17dvD>xcCM3|D1Q(JR+a!MIX>U%f6OL8Fe?ZB`JGIFFaZ5
z=M`#)v@UXe(J)_c6LPQmzQM%SL$*`Z-8QV5l+GnOvMaBH#NjS>RNXOcd534WLTrjl
zB9S2GtT?@NgUs?%chvYepeN=WZ;3CxxV7hL5~BHega-U|C(_-pwsN
zZcQJF(W-Pv%BWlOmu!~&6;OYaerwGBPw~|MIx_wYcP;wqA299LTK@sl-ZY#26CwVk
zXaDNq@g8CqD&7AP6g|&*66^R|Ya*Y@l_wz}0{EeoQ+tBRK%eK13b5vF?uM&&kp{sn
zk{!8Qqms<(wqv~MXh6MMyt3{Gk32E<3tQ>;%>I@_5Y%RmfI>)>jCDE9FqK*KLhxI?
z{wGS4xO}qrmqJFW@RJuhksyi@#rSW&QX+7YpDmo+eB)2yH5!enwsVIH!%ez_2wx#3
zEUkf65y*krJBipgpK7Jej`y|P`#Te(EVcUxFXB>~y`%Q`p6q
z&>WdY@sRo>?rUIvq+W?x5ckm=wl-CB)7uYepx;g>zKcqDHSbTL#yt~N{6v=i&KiA2
zJNUsO4bg48M&c<_Fp8V>t_r%JI~XCU1$YngjJnl2@xZ;fc8pU-uY)rLI`w!IeO`eYIR~k-%TinpkXZm
zYjM{BCz$w~6i?Y;f>gi-(TjT$m9(w6-Grn+QLd{hXU!8EAW<{oO_|P1`PzZ_5$f5JE!c(k}^VSVU4F;Z4&!!?tJ6GW_4U6J?BVjrP
zyg8{=!c-$FY*WEiXr`&W?fVig)6|KCmRfaxYb=zE$LoL0LchTRwg4JDLEd3-2OYiY
z;{%Y(pi7Jli2A1E55{@XZ)Ue}%FKn*?43x)=qywhxMyBGSL`w*aOM=)W!i5EWPC06
zy<*5aH>!LG_1UKiTQ*uZsmlTa!V{cE{N9T$-wo6g9<-%j
zlvV|ZFZX?)-hT2vzcK;+h`wa={QYvL5X6(s{ZB6OkD^drSF|ZZ_wF|cs@Tnu&$Nd=
zDdfE{@#9Wyun`_@c_Kg}#Ag)61!&)a-xIju=cP?@A8E@mZT=$i-+Pr@iu3`Ldu)OE
zcT9{pWQN8iy7QkerNqDXcmDL_426mK1z3c#EFCPJ92cgFWEuv3{CoU>=E&o2LeLe*V4e6T4op
zUG-RgZlrOPD07=zEPB<1{HkU7CWsfCqY$SWYWBo#qy+|k*~YNV#18BRkuB`r=M%yw
zK4|qB5`6dTkhX;IXfANq_hbzu4r+rM%=thDKE^OOx94TYUyOMn3+$XOUCkT)&P}E6
zVn*-^mQpS=iy7LRq8Vq83>Km6-pUq!{&VF2)zjZ%&9c-uf+fL!Fg@>8MnG<8PbLJC
zBi>+Rw$bBBQ5!>Xn6Ot~A0IZQ-l>5UZnNlh=l{V_lMRLTY~ElqqqyeKghYi^6Yc40Y4U9iCH$w_~i7wwRh|F
z^$|(@rMo72->#@T=X0B7iQqFR6AxCm#eNVaZ{)#23j#R?7bM(NI;*L1F-xlIn+?BFXL{fVfEQ$*-45uf%~_bmgJN1vJI
z-5>0EUEH{K@jF&q8s9YipppNeB>J@Kbn4Io+#3jOQs7AAT^8GCCukOqX4@-dkZh=qoXG_eVW4`%QHYapP-Hku4FFoEX@$Lw{$xxecNosGdCd
zHwHgsi3#fwi5(lp^IhKv$LGlTDcXI;$C0Jmokk$GUO0
zmL*Iz4Z*)|^}4i>NOn~)IG(v|T^y^jETzf&FxACRpU8CV4qXKetP-OTvo%h;M(&C-
zd{ee5FqCqa8|XzI5HRSd9^~3u_n-a!m;Pf4B6Iv}pvWJ4++&&Y3=$VLXa1E5LeLa;r8iG4c7jG_^`3ety}
zFF;0urR=iWp0!>f_=lE)1gy8!OcPnshtEo^LYi<~Wh!bk6r;>22{bai!k{k@0gQS=
z2_qF&e*D^2DsHJV58m&oU#HuwtSj{X6LcOmK!`v)v4IR7^Na`0dJc*CR6ZuG7Z
zQl%J+`jERJQ;Wo+BQYtVoW&Z!t+??*tiOao2dWMkWEX$AwxdC_p0;iu`zZL~<(hm(
zVC2g4%5i3RLP{Uq6W%z)5X=1QDC1jnjX~P`NJmz-l+_UIw$_r9B;z;zjGv?*bh_@?
zxpXV4_!9{H9tEXpG)>km!q~>$1D#eccdZaMrJS;BR0Ajn!Cc&r4#AUeDMM^oH@f06
zngjb{#lk~K@0*6~z5+GAZ}OR_WB28>eY-ak_hMh8rA=3w8^ZNC=T7D|21{t!UF>o0#dsxJJW6s>H1XK8z2rR-7XoH^&qGY#8NGt`S~y
zbI|QLO-4%PJVI8iH!wEnLT=}!YF{XgJ@dyPFA7j3dlq^k0Vtsm`^V8`!vHHf$G>(IT)XIv
z>#@gpj#f=JVbF0uxLm$qY$1TWllFckmq@6y(O1>`%_ZE&W&dqPdhKBq;{*I{5ZZMc
zDh#syA*tG;om={Yh|t+FhCYNjg>-(vvW!;Ts`O6KtM6c}M2b#t`aLr$=NOq7KtY
zTz-BktMu~e%JXYz8k2YwYqMK?$;WjP
z2Z4Ks8HUY0d^uENZ!3n8u5M$Eqe*UR4tn4V0iDV^qcf;|yI%k&wE)VO9+~NE<4h=!
zx!o9&-8%dNlr!nJo?x-WbR0p+(?O6n`kuSX9p+MeW8fDAJ?L#EH^S-5Q%nJ^bZukq
zpUVapU%fe9>Nd0txV-QqW;)b7bRLP;!~z{Xi59#1ZHW1!$l=RxCPTcBsP=eG>Z9r%
z@6rBKD0KsC{GlaWz=T|oHk%1i)?hxvcRGDEOn{3LzBfKa&X<#yX*RXL{ZW
zB~<4FUVo~UMzpm`kO$bn0}Tt|o_b}VN>u41kPaf3c|mn@lc+IF-Fx(ltF5?}X!u*d
zf20TAppJX3J`MtBFpfX`3!gErg_#5~=`c`q*V0w|uc4a5kFT4jr@n~W#g?EzSH0FV
zw7N@lRhd)T;ODs`tO{7Xg5Q9VjeSrGET*qDHbYCI6T@AXfq>oWV;0tYrNCK`|JL(G
zQWqGod5SAPVH#$jK~)KhushXEZp0qp7`>{8r10{I|i<&7^fVXjarjEvM
zd(BIWoihVT%x!Bd<1v#%-n|n!w_5ZTM#rRoP!tb-UUI@8;qU>7DqbVbwNXb_py}iJ
zjqXC)K>gbZmBTT{F8FNNz_kJz@D)acGGkTNQ8+>7c|;M(K}axra(dnu{yoz|V7Hs;
z#eeE;zs;Dx%|?IDxRiy&k=iPSXrq`LtJVyj+8m;iikvceHu!ZsLAuEIeF$<{8kL@q
zAZ?K;{<}acL)>5ysqnI%kZCE3leqj47^8Uxy~cO282M01-oVk`mw+lVMB+nGQq?P83yeYLs|Eewv+A_*d$u`BFR5jNOQIEkXhISxEZnFwc*lgLd;%MeC1O;~>&VRcpzlx4P~4^N-vCN7554J0<#=1cTLT
z?He`pV+`9I^?fQCuZ;_mOL>O5SW)IL4m14ahI~PWeB1&*_Tg~<*$8T6qsAy&R@Im?
zUgl0a(DjTrA#yfvy0-*@!kiX
z(?fDJ#O-h!sq}Sf0Kl>B&42JHe#q=
zsyl6X(Y{N1dRCwD6(KRkm7_vz>`sdy`NWIm0P=tcdBys~IfOKQ5tk_ED}rI~pz>+$
zP4dor3YS*x*Ml>n28+BeMfm#UwGkGvqZT5Pp_do2MX$}KpP!XGTexDkeerxUT%ow2
z#cX;%haiSo<%JSIHZvs;rIhaozW7$4%LMu%b>(?@8qMOB4P!Ag(w>Xn+5A%Ly2T
zZ7w*nxJ$KKtJR4sjKe}hv=*}+9%M2MH)M_WRL8cQT$7#*
zden`v5l6%qE(XJsq
z6ux!nk;qQKn1n=MS@G;xt6+D53qZ9Wf&uG8O-?L)x|uQM-uS2j$mBu!p(@i?!+8R9
zjD(8N9KJM-IlZ^~_Jv4a%m%y;aVd~%#HpQ70`i)1!;7Unrx-h}r28Osn`|iIW(H)O
zbs}0}Z#IR7D`l>9aa2GT$DBcwYQvS}L1QISkN^(2U%++q9jsI)}i{Gftp$OXMSH*rQJzggC#?R7ql
zEb3>K>;`s@%C>2syU3`-d>oE!4?}^Th|}1c5X5j1Iz6}$$m0Tomr3bNTA}9bVl-p)2%`&b^
zrga6M@Pm;%%@;}8ui~jnxm~9Bwz^;jU3Dzu%9$=95G3-Ub~>>si#}a1&C#kT4jD$>
zp{(2XCdFWx7qSYcnX
zf!=YXi`ph9c*mvAV7Es}qwC5B3`b>_^7)MyP-A6y6ewVhg|~((wer=pu1GF9!>uCz
zAM)>qI>4{a`Th%jn8Xkm_at91^oCmO7eE&)XE=&~4QXq@q!l;hC{dY7f<_}A3LQQ1AbFWhYYq4j
z;ue>cTeW^fipSU5P$)Ueojz5(sm1zKF
zSt!p3z+`?%
z_utta)2Bz6KTk>t^zCCohC~k%VPbnmJ9saABnwWn%8}^Y7|U>{zCfl$xdr!u$N!p$
zP6s#AWfs!%#VWfXJOX!{%9Z0g0z%0MT*!GRm5Jw+C=3ZG7%
z9S&W*4hQDdVefnYZt4*sz9iB3VO&iW;mtXR1a%{3OD_9nuHb^k9Rp7T3p;rGLv8o<
zLqvE2V4f*fQ#B)-m|;;#*TJWqK0q%y5qiVqrTqioYTO;P?a7}0tA!``zL)M)oj|@q
z;wi=PH`DDO-iiQxg*S=ePn#@i%|E;;YrT0QQl%LnMt&`$y7rM)G__VZI-S=?XF$oL
zQ)X>4@&aWzL59>hRno6^HG7ToFt&e5bP8M)IU75(NXtjWpW8p+TWliQx9s$+#7s>Y
z!I=Ih2K#aN-w3pph$mC;ZL@4kOUvZx7n$h`cf10~?t`IAb9r63pg4_nlXWjYFBtbl
z$y;1Mt7t4dl_7d9n{4%0%Y&0fo4n98T?f;$+srz6;ID;NO%DYu+Fh8;3a^gf>R1P1
znUjleN`6SXN*8`X9Z9f33tA1eC|r=XB%65UxXqDB2-4iTuGVge*p_sr=ct94@0@)#
z3QHp5_(kpy@{Z*{*8^7dLt*m-*~@_S9?8EyOf>(xmfVpysSRh8;3}Qz=%cYM?agnh
zuEA8*DqK$>fdlvj@h&+lEa%0&$)X^$wAU9Z%Z*IIx3Rb1k-cTfE-P8}TT!6BxhH
zM{S|*4!ZiR@TVLBp7vEKH0KHK&9}&T+s0X_DxwSGcfH~dEYqe>CC3Ski{%RQXXVY1
zL&~LU6nN+QoBUHWxT28IQo!Kx@`ox!mZ8T5cv3BS53g+2Tr_yEJTK3oKZ)we+J=dc
zrL;d@^62fDgPq=${wb#X@f{vZKfH9{r2P{`IWN1QOK35oCR%9rm>-Uon)qjdWio_(
zCIU
z)6iVwfH=U)6+FYU6W6DWj!_!S2DHln)Y1g37lBb~`q9zZXMFo8Mww84@%gi0KI+gI
zQF}1-hfoE-yo)a4Joq0q9&DDWUOXXT(beU2#%OJOZWD9KhWykG3=+!4rUD-W;~TsD
zj|n2&8c2G)iNk;ho9bqWDu#}<7~ZzB#0szBIx~hL#qRSQrFdWQF45DnPpQ)RObScP
zfawHW{OC}EY}^mUaK$;a=^HZoQ#t;YGD5t&5d~hEZxqqWE2EsE*>v1yq?@Gm8VW-f
zW107lv<$2l^QpQnAXC@61SH$EYr4+b$SB|V^DFIB9Au-uR)1Qe2-MJew)i6I${Wj6
zI|jz=ErtH^0+Pw6NMKL$b!Bj>on|-MH^EWp8>`$@m
zzWK64twiH3sOlZ*nU7CZ{E`j|@?rA|$Kx4599%+pp81Ua88weCd3+eh%2eeM@l^+^
z`2kyX9uiv?fft!+qc3+T98f<_3AbQLaC2r3Jto}WGD)N8mwxfbIL(fmzZ7m{;}($g
z_8%hdPcJGtSmvA9Cm7|wY_smmpEb+gR%U(tyA1ueI`m^#5>v*0)bYP7XTSS3JjQu`
zsLh?jnRtA_7ynuV6zfqf7E;v)S^X#FjS|GLOO
zXpnz3b^gmDVVlo?kI?H+=EF}4V!ydA|6YBbSmYm0r{5ot`o)_5wUqwzSbjDq_lTzi
z?teXqUmLFmy`pV%VF)~mv{jh-90n%_(Y4UFrDM_HA-49iHRNNz2^^)~eIse3Po?5u
zjc4<7Js7pOovL8f8X_eNpp(
z(HrSnSfigdeg3zkXEQ++i8f~>?Rw>IsnERN$%~##O~j&vW@p4WMi1G)uD-k))&8@(
z{J35A%d6hS)meyB^ym$uoF;xd~SzZ?=Xg|!G#N;%bPyJdl
zVVa5frqoGlYiI?DGH*vnb>lb%e_Ow`y3=}Vt~9DrK1rRlt^#Lx*%~2QWR{%l`JR>*
z-oIMU|6?Xu^QZ_`V)R>;xx7qzL6R6`V@K~ZP&{NUNv@(-~qqX13_;Zfay#pF45wcQWzG
zg`<`$LQKeU-Boe47M-rTp``@*og%K;?+MblT1zmm%JvL7=}tT-#W|ugq9YuorRZ^H
zTUK6B`D=8YXLY?RS+@lv@Vu#F>dY89+iBEqvcqNPKP?+C#-Z#0O{=PMFDLr8c;Kw*
zuZX17TWAD566zF?ojG3x+;yh((5Hv2+EJuJ1b7DO#>Mn;sCi?m2wE=;akUrdJ=HT_
zs6l9kc=;b0@Ui7GtdT~&?sC$z5YogyJ#CnMF(mLn(CB^j%0aIaz1|My`FndTL?Mta
zfH;l-akxc9FhkDWJ{u}_Pgz{MFRHku#c~5{&Sj@k^jo(QC(Vpf;RL5Ig6{E@<;kZw
z#kPRRrS?)m!Lzc!Q>P#qG`IO~P{6F*n%-NGT_}|Il3eg$3boU~{h$?0ePMZ*K}@&p
zBvIXxL@(sz!B6fo%JtC+v`UzuR(SR(sRrS1WM0d|D$u01vwO-6!nsZae1
zR*G2lleEA6(V9YitDa6$pj_O92ge9{chESzhLCnHA0)Vguro~bI-QKa841&@T467~
zUmtp4abUzQzt#N&>J)KW*r|FA2eX(=m$TP9h?xrF*p_ux#U3gl
z?&*YcVIpu1{kJ*}g`HMz#1BYAI9J!m_J^0M1W~zEMrAnm0omCo7|W1Wogg?zD1#kY
zJcxRUXm*UU+d9LpE|u=UrOzNx`xC;;65XD~Bg?%;8zVKSy;OYdCFh+mAr<3@<@nB1
zGFP9m=N@mTmXKz)Q#+YbT!=J8;0s6FBbGnv{fdYGYAQY3z;
zE-PTLjyTmI&;l9snluMz$+k9w0GIjkc}(k?MlIUgmgZ+mR1&r&o9XX1m*cLYH{4p4
z3fg>cnQUJ6TV^gJ9t*ZFV38Ee@+~;|vFw9&ZtL_ksYPJLP-$xF)+OLq+<|7E&NAS_
zX3*B4#>La`vBn-GXo(^-K0_fu0)Txn=V!_D9#OgE6YbW`ATb3GTox^d17aK!R=xs7
z;@^9|8TA5{}
zFb1k}70bON%!+%0MMkMYB5R<>A4iGlc;3lGB#l?ere&pEujtjnH1-w;w11f1Xj@pZ
zeZ63(+CdB7eDU>Mjd(Ah2i(6raq)JtoDS;!8?R@;erk}gLLVV&@iU~5!ZmJ>U>rJ|
za!cWPQq)mo64{~5h41$BeYqLaq)q+Hk|Ge8xL+hvB8R%1TwqgPPaZ*h6qlVGmJnu!
zlK`yy0%Vy
z9aYP|q5-E%(yce1?qxg>6}TxbEg$cva!ri<8#PZ-*=I=>E5g|w<4%H*)u>CjqYzfU
zxfRoySoL#Cb|=8JwvGGcB#Lo#K(--8E1jDh+0;r*D0MPI;xPjrADDacIB*S6Pfk+C
zW(n2w4yJFPZ7y+(L5g_nU9CBz28tCK>w_%EDP7z|AaXYKGASOb=2FX>-q$`v!5?jvk>38|HS_1G`3IbkS%9XYotCO_2r^g@OpF3Yarul3)MBA=X7nCkRP{3U4GMt``hB0GBoI|W
zq>z|aTP*vp*&!~T?=}E>8zDQ=8$9_wqXB%lN6qUy(8c!)^i$6#wn9LjM?0c#D;XPh
z(_u}LXg-LbmCmL=t5z;q0Z?T5Q+N&q29m<@i{*PU0)vUcE13{=diD3M=7etvP`gyn
zx;ADyJF
zS)15tU$*Q{daL((Qil%m?`L6|`JhqOIXV(Wk#C)z$*-`PZ#_TIjzl|S)Gdxjz1~HJ
zCgZG*p^=6zrXYq-;WoFwsZ09E_9aGxMc{ZyM*O9T`L>}X$Bw78(L`GzalXs+$|aMIL{cwn=hI~Y>gX~0Q*GpM&DwUQq6sit=<`xTVs
z)Ai}YjolmM+*21?zV*+{x-w%+EsPHgmmBF$B78?hC&YEi%1k2BgSZT>uXaiU`2&YI
zlRkLbU~5*VD$Jst3a`<34{wOJ4*0l@I}rly4wY
zy3yKS=L-6S@$rZ#Lzy_(d$%*@R7{ecpDFr+Gfw(dGYNl6rjsz!64l4H&Jl)B#Fm2K
zK)ksPIQ9eR${j-Ev-1d8^*(k4r3+{@FK=Zx(i`g
z0`Wv&_Hhjn8VEd7ImUzjpvaaTy?2vO1
z0v;6nmaaipSej3t*q1MoA6~LXO0dRe-LKv_%kz{IQN22AFJkK=Uf4WqXfJ>#8siDH
z^XPMYKW?B>(56HqS*xQP2(zs`wmyT80l3(*!7cpAJkV^!B^#J#(FFf$8q1V_kOYGp
z{xtfH`d_#x
z5EByE`or78XGE5Dsapj3fk2GCklRrtUGmks*xfLXthtFk_dxR&&vs&7GxUUh%%fOB
z;RCW|Vc$aPWrOVG(l9j5lbG?WQ?!BB)+y0TCzV-4B*;c{|D0?Z1oru5siy3mb1Mof
zlTL*#Qi?1q=?(@-7OdLGS6YosvA)y%UzZYJ%1GBRma#uH5r|(_dX_oXNm*j&J
zn;!6=Vr)H<<9=7$%hRq|sXqy`dzEu;Dr@!d`Cx;to$7f{6ouuRyTlTsz4Ns6I`)pi
zdS~ztOYNZ0mI?JfI(fNmPFNK(
zp_tpUrKFG1Jke9Aq+z{YsvJVhrHY}O*sq3&40uVyOgXd(-X71Q=P9BeKU}OuvCR@3
zJR~&zJSDUA_9|f!d23c?#oDL8-f{2?%!-X8`&JJ#Tul~El!^y0trwl>Y5AFykYA3c
zzMf{a&_Kl}uw3=PxfKovM^$TYm@l@sQT1LEhcLF3DQ#$0wr-0ptGu}fNW;(smdVkn
z%1Lr26*i>1^xJTbp_u(N^ck$-VZo|<7#6t!^%->6xb-T=Lo2(
z208ERa4~z=^_HIx@(?UBSHW}a0tXZp#(UjFNrC$=q
z6p0sd9lQ`ZgxX_ldW$uiA<-9VBMaaikG$kQem(LHeVFM9>$HSUCzPLv4~N>%?}Kq|
zP9Z7A7Ya>fu)||I;ZIMv79&G>$qE@KAK&+JRnWc!TfofsO;+JVrFLraw3ilzXYWN_erNj2`WRGpr(QlE2Y{0xB<$kO
z&DQE|fn5Vkl}UuT-l*gWF@m0du8DzIt?{y=5P_NWsA}XP&cRJ%gFpN9#hS=eV)a2d
zm)NCG%y03d-xS)I)z{8q-E54&Ce`jJoGn`<<2-RC>
zKCY4L4#)(#H=}l=c#|G*c!AHjVlC>iMrHa9Ctn!ept}gA`t}qLxsSgP|9qf*&n&bf
z*Xn0L8o1M^6omi!t4~QaCL8!su
z&G?@=5lp;9%8cqIFwGZqBAY>a7_(>7aSevQD8eaS`?Ob7?V5pet)7EGT4|Yv!8vyx
zO4{VgT@nM_;*?>EsbP2+(6O{-1n!)fEeRZgt-NI*#SW-YduysYs^ClH7b8aIw>%be
zMO(&-wdH^a|E?eMVxwR|mu*25#W3Wy0ezU0kx=ED@(^uf&z*UA8S5ftjxg?w_wjpW^`W9`DF!RW$sv11OI1xz-#Zj
ztK`HMMQ>;D@Zo5_mLR`r^9!?O)p7ZObh`nY0{wO2`VVPd6qT3HX2`RY-y$7zMt_Yc
zKji?X^m2Vzt9dEdVj7h<&s=uvqo;iLwP8QQ7+H>8JP
z;wbj3P&9E~J(H5nyutHj=NGTJ)noNl@kl=H-llrXPL7`?fw!M$r|@3zS?r3WuHfgo
z8k=^OO(jF|DuE23o#3To6sA=t8-Xyl
zTNr}yEn0UPy~1Q=w=tq?GVrm;)mq|aUS@SX{CZtvEN+sb8me)^ER8|ME7qy#t%(&>
z7C0$JCbomQ!V7iuK%F+@?pE18Fmag2?x>sDT7lg>)
ztTHhav`k{j4qtxP66!FH47>zzaU12yy;9R;Ms2~6XTZz`86Z2C%1A?o;xfYh+{YL(?{z`9
zf$YcW1F3@t@X*gq?i&e|W+0q}lTj=L3y0#f#Jf8gXyMV7k(VMdd(jM(q4-~0q{EIt
zm2fx`o?)`w9!RNGAMUgT4sR57QKDWLrJ6l@d$8c-RSv(2)DF|WY02tmnSq3x?c
z?4lq|Yb_I}-nB52Ic>Wm>Vamva>TO^sgv%KI1U?Z=lf1Pmhm}YKjs@JV{Z1MDVSBU
zjdQ4)t&!y|wvJ3+$`0jV%8dYW3sxc41a7ZASxFI?VRuWW`#ErS8WqT70zRQYLK=02
z$Qo2in|ApeUn~?gk!jAge7D8nCgvb$Voc52{L5LDpq$0kDP}DDqy_|!H_D)XH)X+K
z<-?m2rtcT35#CQjyCE&=>l8MyzW>NZNTa=nchh^n_-Z^O8Z$C1I}aNQG^0j@HiC^=a4V+CzWM=&gC)a;mggGi#2VkYx6e(>7d*Lwx}tr)*7VjFP{3Wh@r
zJ=ygIaKUdpZn0$L@K|1}3J~O|cxURt9GVJg9TrAhK3H|&UE~J8U*5r@zV8+#G+9qx
z)M+(9xWH0f4+`U~hrfnbnE$HA2VEw5Ep3Fkj{73jh*uMgNkq}kV7Zy-tJdB^9mT`d@QIQUYssP4C^$eeSv5I#d;(<-
z7n9tmL4-`f^GTUu!p)!chVkWjkDBT47&Wa;`tMK}2?(`0sdu$W*or_Tq;d
zO;1chmd9TDNo9?fNwOp4#b8rvjTSR;zX9EP2bSq@CMw6ppI?3ag2XxsjR1u6{}AV-u~P5V~hO)_yr^<;XAx<1E$|wGbf7;(gaecH`jYO#lj${
zE%qr&9J}IXMeer>gOI@4)irk8_(+|b{kkw;J
z;eL4W^Hw7oR^GYdD##JE%2$6DCZCL}D8By(fIxr0iY!#n=~}|)F_CY3vf-1`F%@Nr
zp*|AIWqF8(NX^K|JARQ_dH{m>npFED#h5mFiQ;n*oZ)fOw4mGWF_@#j9yCu)LM1eq
z0;naHL^8dvPEm`!psMEb01rbv?^JX69e64T$J%pPG&y^K*H$+*z}Qr^AR?dz%w1`Q4YOj!Oy
zOp*gR>hkR%HUIE@wB%ZGXJ3~{6F#7jn}-G@ZFY3uH+z$tm7!{Uh=K8fDu@So@l@KG
zD{)Xje;r=8O^$)i7aHy_;_|wk*m5SNrWx(}`SZgjoKQr%pdTr0EZFjtGm7^D{J8do
zM?)s&T~03tq8DvNlwB02_#)*ok%@B=Vo@E=FqB}gnU)Mtn}FpDBF#;ou;ZL3KaAul
z;~}xer9Z2(cAjyX!yhDwwGm#)SAb3Xf#?7Tr0LgTS%j?$p{V@|w`@PzkCfX^Il6P5
zjZlTUJKQnm6D1-lF%$u;CvVj{0J@Z>7*Hq88jg`4f=Qt4J+H}~Y+{Kvu?{IeveT=Vt5JjP3Qf1N!>0*wUe~%*
zu=VEaEh7^ib?dVwr&F+X@=CuhHu|HM!Z*;V^1wc|@n*|`Ef4GXATHInf(IGnp`SOX
zJ$>B`-hVI(Fk(0eANiH}+1c6I)p@-jYv(S1ND9jrVSNo42!th5+!L?>000000VI`3
z-2eap0iKfE9Jyj4OzAhvYhk`ww_pjF$-s1)68Wo6os}L`_p2(DwPyWM?+^&Fk`q--
zKLrTYfB4KNdMzD)@w~Tzj1!rpx1NZ$MT}U1C19>>PGP|D$Gs`I4i$KO)W^kAMfAHG
zV}wEIAj8cG-lJbc?I=-T#n4aVaICUqT>aM>70fdpruW$sS&sE(6#y#gH9~g@%+ocN
zRDUAnO=s59PAU&e-N5Gg&w>mOGrGZ(Dr&z1M&BMthn?fhSHWUSK)&>3G5c^08aZ(S
zhFz4{l+vLw%=Le(qZ~g-B_2~$?`jM5CV}1-s|pRis)E88znHqq0B}$#N|w!y)Z2rr
zXRK2J?I#hxeNX@Z06)*5oc+DM53%_L3W(ceC_v)aU6i0VO_pmVS!GTQN(9*}`{Mk^
zfygL*>wCQQQ=3O1K9CZFfyLubbQ|$$I
z{%ty`B3+sR6SqVzymGLt7D1a-tzze00000000008jLuH=-SR|Np%IJ
z{M&JW37#`o;Rb1y-8NnT0000Aag2mO(cR4%y32z9Pk1$w9}+$o&Hq+x;p>ae;HSULgh40lvoo
zMGz__FS(OwG4M)OYiqC-2?-hGBg(DUb12V__y2P$#a}vOh^2Zm4()wxwJ-KFn>sxU
zt^2sd%SLF2fq%09n4(H_0T$VD59mcsju9dR(aC$c6W@1!3};DppIOkr0g_gyCrzej
z^P}adY3DHP1q*|9rwOL8C|{fU3HRg=t5&uMa8`*9|9k4Pz+C|+yWG-q3oLgUx!p~*
z(@Yu~k!Z|khP%b7erL6VMYf}c85J>92H41FEXLrS7m3>ZZ4%EMtvFk!
zJ2e?Z(=DGexb!vr@&XewikG>!bO_?}0~Y%hQQu*HQQqzh3UNr#D;DF7duo>bBU!J+
z1(EG!i4T3X*sC-(X}~|!s8RQJ{Rm4m_w4Mu777LE#>yq;?phOVKQpamG7v?9D-n2isd!BPGAZqoMQO?VYW^w294kB=5$P@!
z${+=D*ln42^wq!y-~a#s0Dg{9JA4)f{2N+9-;!7k5&YXhWJk51LH_bBi2`{Yo_(@pP@!~vYEbn5W8AK6I5}}t@
zsbQaO1F+LcpwKo3`eTE0hqn1#=n~H@Q0RWVrNUaC7`Nc(9GqbST*^
z)c{Fz2F2TfHn(e&$oj1S@(jKS#*%^aG*#Ot&y9`KP5I<1
zP>Zhp39!vh>A(DiuCzP