Vancetope Cortex — Specification
Status: v1. Binding product spec for the unified Chat + Document + Execute work environment of the web UI. Implementation resides in
client_web/packages/vance-face/src/cortex/plus the backend undervance-brain/src/main/java/de/mhus/vance/brain/{python,script}/cortex/.
See also: web-ui.md script-engine.md user-interaction.md
1. Definition and Scope
Cortex is the Project-bound work environment of the web UI: Chat, documents, and script execution in a browser Session, with the Chat Agent as an active companion to an edited Doc tab. Cortex replaces the older ScriptCortex (scripts.html), which no longer exists.
What Cortex is:
- A single editor (
cortex.html) as a Multi-Page App entry. - Project file tree, multiple documents as tabs, persistent Chat panel on the right with a Help sub-tab.
- Client tools (WS) dynamically registered with the Brain, allowing the Agent to directly edit the chat-bound Doc.
- Run surface for executable Doc types (
.js/.pycurrently), with Validate + Slart-Generate/Update as additional actions for JavaScript.
What Cortex is not:
- Not a real-time status mirror beyond the Chat — other editor data arrives via REST snapshot on page load (see web-ui.md §1).
- Not a second Auth context: uses the same JWT Session as
chat.html. - No dedicated persistence layer: all edits go through the normal
documents-REST endpoints. - No proprietary scripting language. Cortex relies on the Brain-side
ScriptCortexController(JavaScript via GraalJS) andPythonCortexController(Python via ExecManager + venv).
2. Entry and Data Model
Cortex is opened exclusively from within a Chat. chat.html?sessionId=X has an “Open in Cortex” button → cortex.html?sessionId=X. The Chat has a Project (mandatory in the Chat model), so the Cortex Project is known. No in-place Project change — different Project = new Cortex Session (new browser tab or close+open).
ChatSessionDocument carries two Cortex fields:
openDocumentIds: List<String>— order of open tabs during the last Cortex visit.chatBoundDocumentId: String?— the Doc the Agent is working on (tools operate on it). Must be included inopenDocumentIdsornull.
Persisted via PUT /brain/{tenant}/sessions/{id}/cortex-state with body { openDocumentIds, chatBoundDocumentId }. Restored on mount of the Cortex app.
3. Layout
+---------------------------------------------------------+
| Cortex · <Project> · <Chat Title> [x] |
+-----------+----------------------------------+----------+
| | [Tab A *] [Tab B] [Tab C] [+] | [Chat][Help]
| File Tree +----------------------------------+----------+
| | | |
| | Document Tab Shell | Chat |
| | (Toolbar + Body) | History |
| | | |
| | | [input] |
+-----------+----------------------------------+----------+
Three zones:
- Sidebar (left):
FileTreeSidebaracross all Doc paths of the Project (samedocuments-API asdocuments.html). - Main (center):
EditorTabsfor open Docs plus aDocumentTabShellfor the active tab. - Right-Panel (right):
CortexRightPanelwith two sub-tabs —ChatandHelp. Help changes per active Doc (see §6).
4. DocumentTabShell — Body Dispatch
A single tab shell that renders each Doc Kind. Resolver: resolveBinding(doc): DocTypeBinding. Lookup order:
@vance/kind-registryfirst —resolveKindFor(doc.kind, doc.mimeType). Covers addon-contributed Kinds (e.g., Calendar) plus all Built-ins migrated to the Registry. MountskindEntry.editor ?? kindEntry.view. UseskindEntry.parse/kindEntry.serializeif available, otherwise the View is fed with:document="DocumentDto"instead of:doc="model".- Hand-rolled Bindings for Kinds that
DocumentApp.vue(insrc/document/) still hardcodes dispatching:tree,list,checklist,records,chart,sheet,graph,mindmap,slides,diagram, plusimage. Bindings directly reference the Views fromsrc/document/— no Cortex-specific renderer classes. - Catch-all
code-Binding —CodeEditorfrom@vance/componentson the rawinlineText.
Four modes of the BindingMode-Enum:
'code'— CodeEditor with text selection mirroring to the Cortex store (fordoc_get_selection).'image'—ImageView, read-only.'typed-model'— Codec-parsedinlineText→ typed Model → View with:doc. On@update:doc, it is serialized back.'kind-registry'— structurally identical totyped-model, source of the pair is theKindEntry.
Shell Toolbar (always): Reload, Path, View/Edit-Toggle (typed-model + kind-registry only), Properties-Deep-Link (↗) to documents.html, Dirty-Indicator (●), Debug-Pill [binding-id] mime-type with Tooltip binding=… mode=… kind=… mime=….
Toolbar per mode additionally (see §5).
Parse-Error-Fallback: typed-model + kind-registry parse inlineText on-render. In case of an error, the Shell shows an error banner and a CodeEditor on the raw text, allowing the user to fix the malformed file.
5. Run / Validate / Slart — Language Adapters
Run capability is orthogonal to the Doc Type Binding. resolveRunAdapter(doc): RunAdapter | null finds the appropriate language adapter per Doc.
5.1 RunAdapter
Interface:
interface RunAdapter {
id: string; // 'js' | 'py' | …
label: string; // 'Run JS' | 'Run Python'
matches(doc): boolean; // mime / extension
execute(input): Promise<RunHandle>; // start + return live handle
}
interface RunHandle {
id: string;
state: Ref<RunState>; // 'idle'|'starting'|'running'|'finished'|'failed'|'cancelled'
logLines: Ref<string[]>;
result: Ref<unknown>;
error: Ref<string | null>;
durationMs: Ref<number | null>;
cancel(): Promise<void>;
detach(): void;
}
Active adapters:
| Adapter | Match | Backend Endpoint | Transport | Log Streaming |
|---|---|---|---|---|
jsRunner |
.js / .mjs / .mjsh / .cjs or */javascript |
POST /brain/{tenant}/scripts/execute + script-execution-* WS events |
WebSocket Push + Polling Fallback | Live |
pythonRunner |
.py or text/x-python |
POST /brain/{tenant}/python/execute + GET /brain/{tenant}/python/executions/{id} |
REST + Polling (1.5 s interval) | ~1.5 s latency |
An adapter is registered in cortex/runners/runnerRegistry.ts. New language (Shell, R, …) = new adapter entry, Shell + Backend-REST analogous.
Save-before-Run: If the tab is dirty, the Shell flushes synchronously before the adapter call (store.saveTab). The backend path loads the Doc body via scriptId from MongoDB — without pre-save, the previous state would run.
UI in Toolbar: ▶ Run X button, Args input (single-line JSON, default {}), Cancel button during Running. Log panel slides up below the editor (collapsible, max 45% height), shows Status badge + Duration + color-coded log lines + Result. Close button detaches the Handle (backend job continues).
5.1a Script Document API
Every Cortex Run has internal access to the Project documents of its owner Project via vance.documents.* — read, write, list, exists, delete, meta. JS sees this as a native host binding, Python via bundled import vance over loopback-REST. The contract, ENV variables, SCRIPT_RUN-JWT format, and label convention are defined in script-document-api.md — the cortex.runId label described there flows back into the Runs listing logic (future Cortex Runs panel).
5.2 PEP 723 Inline-Dependencies (Python)
PythonExecutionService parses the PEP-723 block (# /// script ... # ///) in the Python source and installs declared dependencies into the Project’s venv before the script runs. A hash marker .vance_inline_deps_hash in the RootDir caches the last successfully installed state — unchanged Deps skip the pip step. Failed pip leaves the marker unchanged; the next Run attempts again.
5.3 Validate + Slart (JavaScript only)
Additional toolbar buttons if runAdapter.id === 'js':
✓ ValidateopensCortexValidateDialog(Modal). Quick-Validate (Parse + JSDoc-Header + Tool-Allowlist viaPOST /scripts/validate) + Deep-Validate (LLM-Review viaPOST /scripts/validate-deep, server-side cached by content hash). Backend internally delegates both endpoints toHactarService.validate(...)andHactarService.deepValidate(...)respectively. Cached Deep-Review appears with indicator “matches current” / “content changed since”.✨ Generate(visible if editor is empty / new Script Doc) opensCortexHactarDialogwithmode=CREATE. Description input + Submit viaPOST /scripts/generate { mode: "CREATE", prompt }with polling onGET /scripts/generations/{id}/result. Backend spawns Slart withoutputSchemaType=SCRIPT_JS.✨ Update(visible if editor has content) opens the same dialog withmode=UPDATE, additionally sendsexistingScriptId+ optionalfailureReason(from a last FAILED Run from the Run panel). Slart’sJsScriptArchitectinjects the existing Script as an “EXISTING SCRIPT” block + the failure reason as a “what to fix” hint into the user prompt. “Apply to editor” writes the new code viaupdate-Emit through the normal pipeline (dirty → auto-save).
Python has no Validate/Generate endpoints — these buttons are then hidden.
6. Chat-Binding and Help-Tab
Bind-Pointer: At most one Doc is chat-bound at a time. Topbar shows the bind status as 🔗 <status>; Agent’s tools target the bound Doc. The bound Doc is sent per turn as ProcessSteerRequest.boundDocumentId (per-turn LLM context, not persisted).
Bind Mode (toggleable via the Chat menu in the Topbar):
auto(Default): the bound Doc follows the active tab — the Agent always sees the Doc the user currently has open. Additionally, the current text selection of the active tab travels asboundDocSelection({from,to}-Range, only if non-empty and in the bound Doc) with the Steer; the model reads the selected text on-demand viadoc_get_selection.pinned: the bound Doc is fixed to a specific Doc (menu “Pin to current tab”), regardless of the active tab. If the pinned tab is closed, the mode automatically reverts toauto.off(menu “Unbind chat”): nothing is bound, no selection is sent.
Mode and, if applicable, pinned Doc are persisted in the per-Session sessionStorage-state.
URL-Sync: the active tab is mirrored in ?doc=<documentId>; browser back/forward navigates through the tab history.
Right-Panel — Chat + Help: CortexRightPanel has two sub-tabs.
- Chat —
CortexChatPanel, own WS connection, Tool Service attachment, message stream. Remains mounted (v-show) when Help is active, so the WS and message buffer survive. - Help —
CortexHelpPanel. Loads Markdown viauseHelp-Composable (help/{lang}/{path}). Path resolution incortex/help.ts:runAdapter.id === 'js'→script-cortex.mdrunAdapter.id === 'py'→python-cortex.mdkind-registry-Binding →doc-kind-<kindId>.md(convention)- Hand-rolled Binding → fixed mapping in
BINDING_HELP - Default →
cortex.md
Missing Help file (404) → hint “No help available”, no crash.
7. Client Tools via WebSocket
Cortex registers a small UI state tool set with the Brain per Session (CortexClientToolService.attach(ws)):
doc_get_selection— returns the current text selection in the active tab (Code + Markdown — typed Views have no selection adapter in v1).cortex_get_active_tab— which Doc is currently in the foreground (can differ from the chat-bound Doc).cortex_open_file— open user tab in Cortex / bring to foreground.
What is NO LONGER here: the body mutation family (cortex_read / cortex_edit / cortex_append / cortex_write). Document edits by the Agent have been running via the regular server-side doc_*-tools since the refactor (planning/cortex-document-invalidation.md). Cortex is informed via DOCUMENT_INVALIDATE-frames on the Session WS and fetches fresh content via REST — buffer coherence via 3-way merge on dirty state.
WS-Frames:
- Client → Brain:
client-tool-register { tools: ToolSpec[] } - Brain → Client:
client-tool-invoke { name, params, correlationId } - Client → Brain:
client-tool-result { correlationId, result, error? } - Brain → Client:
document-invalidate { documentId, path, kind }— Side-Channel-Frame, tells the tab that the server has just mutated a Doc of this Session.
Tool set is static per Session — tab switch does not change the registered set, but only shifts the bind pointer resolution internally.
“AI editing…“-Banner: is currently derived from the frequency of DOCUMENT_INVALIDATE-frames (client-side — no server involvement, no additional LLM contract). Visible as long as frames arrived within the last ~1.5s.
8. Save Strategy
- Auto-Save with Debounce ≈ 2 s per Doc. Watcher on
(id, inlineText.length, dirty)key tuple. - Dirty-Indicator (
●) in tab header + in DocumentTabShell toolbar. - Flush on: tab close, tab switch (away from old tab), Cortex close,
beforeunload-handler. - Pre-Run-Flush synchronously in
onRun()(see §5.1).
store.saveTab(id) writes via PUT /brain/{tenant}/documents/{id}/content with body as raw text. Content-Type is the current Mime of the Doc; the server reclassifies if necessary.
9. Path-Extension-MIME-Bias
The server typically delivers text/javascript/text/x-python/text/typescript for .js/.py/.ts/…, but not every file has this (upload, migration, third-party systems). The Shell derives the language for syntax highlighting from the file extension. Server MIME is a fallback for unknown extensions. Map see effectiveMimeType in DocumentTabShell.vue.
Conversely: the Brain-side DocumentService.mimeFromPath() knows the same extension map and sets the correct Mime when creating via POST /documents if the client does not provide a Mime.
10. Cross-Spec References
| Topic | Where documented |
|---|---|
| MPA Editor Inventory (general) | web-ui.md §3 |
| Mandatory Components + Style Guide | web-ui.md §7 |
| JavaScript Sandbox (GraalJS) | script-engine.md |
| Knowledge Graph + Document Kinds | knowledge-graph.md |
| Inbox + Notification Dispatcher | user-interaction.md |