Workspaces
A workspace gives an agent a durable filesystem, shell, and optional preview ports for one thread. The agent loop still runs in Novo Cloud; model calls and provider keys never enter the workspace.
Use a managed workspace when you want Novo to host the development environment:
const stripeKey = await novo.secrets.create({
name: 'stripe-api-key',
value: process.env.STRIPE_API_KEY!,
});
const secrets = await novo.secretContexts.create({
name: 'payments-env',
bindings: [
{ secretId: stripeKey.id, projection: { type: 'workspace_env', name: 'STRIPE_API_KEY' } },
],
});
const agent = await novo.agents.create({
context: 'You maintain this Next.js app.',
band: 'core',
workspace: {
type: 'managed',
secretContexts: [secrets.id],
resources: 'large',
seed: { type: 'git', url: 'https://github.com/acme/app.git', depth: 1 },
services: [{ name: 'web', command: 'pnpm dev', port: 3000 }],
},
});
Use a remote workspace when you already run your own filesystem and shell adapter:
const env = await novo.environments.register({
type: 'http_workspace',
name: 'prod-devbox',
url: 'https://workspace.acme.dev/novo',
secret: 'env_secret_...',
});
await novo.agents.create({
context: 'You edit customer projects.',
band: 'core',
workspace: { type: 'remote', environmentId: env.id },
});
Filesystem and shell access are configured only through top-level workspace.
The console agent editor mirrors the same workspace mode contract for agent
tool access, while this page documents the full wire shape for server-side
automation.
Managed shell workspaces include Node.js (node, npm, npx), git,
python3 (also reachable as python), and python3 -m pip. pytest is not
preinstalled; use python3 -m unittest or install pytest in the workspace when a
test suite requires it.
Secrets
Managed workspaces can receive generic Secrets through Secret contexts. The context decides how each secret is projected. In v1, workspace_env projections are injected into shell commands and declared services as environment variables.
Novo does not write .env.local, credential files, shell profiles, or language-specific config files by default. If an agent needs to run a script that reads process.env.STRIPE_API_KEY, attach a context that projects that secret to STRIPE_API_KEY.
Secret env values are attached after model/tool-provided command env, so an agent cannot override a granted secret by passing a conflicting env value. Runtime-owned values such as service PORT still win for declared services. Remote workspaces do not receive managed workspace secret contexts.
Office and PDF files
documents is a managed-workspace capability: it lives on the workspace, not on capabilities, because it operates on real workspace files through Novo's managed document processor. It is not available on a remote workspace.
workspace: {
type: 'managed',
documents: true, // default when Novo's document processor is configured
}
Managed workspaces include Novo-managed document processing by default. The agent gets document_read, document_help, document_create, document_edit, document_export, document_render, and pdf_fill_form when the managed Office processor is available.
Document processing is metered as its own thread-scoped managed runtime, not as per-tool media usage or managed-workspace compute. Usage appears with resourceKind: 'managed_document_processor' and may settle after the originating run when Novo finalizes the processor's cumulative compute and transfer counters.
Those tools operate on real files in the workspace. For example, document_edit({ path: "deck.pptx", ... }) writes the edited deck back to deck.pptx; document_export({ path: "deck.pptx", target: "pdf" }) writes deck.pdf; legacy and macro-enabled .doc, .docm, .ppt, .pptm, .xls, .xlsm, .odt, .ods, .odp, .rtf, .csv, and .tsv inputs normalize to modern OOXML on write-back while preserving the original file. Macros are not executed or preserved. Tool results return workspace paths plus managed artifact ids for downloads and rendered previews, including page/slide labels in previewArtifacts.
Set workspace: { type: 'managed', documents: false } if the agent should use only filesystem/shell tools for documents.
Media editing
mediaEditing is the other managed-workspace file-operating capability: managed image, audio, and video editing (crop, resize, convert, trim, transcode) over real workspace files. It is off by default and requires shell: true, because the editing toolchain runs as workspace shell commands.
workspace: {
type: 'managed',
shell: true,
mediaEditing: true, // explicit opt-in; needs shell
}
Like documents, the tools read and write real workspace files: an edited image, trimmed clip, or transcoded track is written back to a workspace path. Enabling mediaEditing without shell: true on a managed workspace fails agent creation with a 400 invalid_request_error (code invalid_workspace_config); the message names what is missing. A Novo deployment without the pinned media-editing binary source returns managed_media_editing_unconfigured. Media editing is billed as managed-workspace compute. See Managed media editing for the tool surface and supported formats.
Neither documents nor mediaEditing is available on a remote workspace; both need Novo's managed processor and binary source. For the full capability vocabulary and how these families map across config and the stream, see Capabilities.
Saving and sharing files
Two generic bridges move bytes between the workspace and Novo-managed artifacts. They are available whenever the run has a binary-capable workspace and artifact storage, with no extra capability flag.
workspace_import_artifact({ artifactId, path })writes an existing managed artifact into the workspace as a file (e.g. to edit a prior generation or upload).workspace_export_artifact({ path, filename?, mimeType? })does the reverse: it saves any workspace file (.svg,.md,.csv, a generated.pdf, an arbitrary.bin) as a managed artifact. The agent thinks in paths ("I madediagram.svg, now save it"), not artifact kinds. Bytes are read by Novo in the worker, so it works even when the workspace has no outbound network.
Directory delivery stays a file operation: create an archive such as source.tar.gz inside the workspace, then export that archive file. If the workspace has no shell, export the required files individually.
workspace_export_artifact returns two clearly separated identities:
{
type: 'artifact',
path: 'diagram.svg',
filename: 'diagram.svg',
// SDK transfer handle: opaque, retention-scoped (~30 days). Use for download
// (GET /v1/artifacts/:id), view_media, or workspace_import_artifact. NOT a
// durable id; do not parse it or assume permanence.
artifact: { id, kind, mimeType, bytes, sha256, url?, urlSource? },
// Present only when `artifacts.exports` is configured and includes workspace
// exports: the durable object your handler saved (e.g. a Drive ref + URL).
// Depend on this for long-term sharing.
export?: { artifactId, url, metadata },
}
With the default artifacts.stream.urls: 'durable-only', artifact.url is the handler's durable URL when the handler returns one; otherwise the ref is id-only. include-novo-signed is the explicit opt-in for temporary Novo signed URLs.
Document tools write ordinary create/edit/fill/export outputs back to workspace paths and return Novo transfer handles for downloads/previews. They are not auto-exported to durable storage. When a document is ready to hand off, the explicit path is workspace_export_artifact({ path }).
Managed artifact kind is inferred from the MIME type: supported image/audio/video formats keep their kind, JSON is json, Office/PDF is document, and everything else, including SVG, Markdown, and CSV, is the generic file kind. The precise type is always carried in mimeType.
GitHub workspace operations
A GitHub App connection always runs through a managed workspace with filesystem, shell, and network access. This keeps GitHub as one complete software-development capability with a durable working tree for edits, tests, commits, branch publication, and pull-request work:
await novo.agents.create({
context: 'Fix issues in acme/app and open pull requests.',
band: 'core',
workspace: { type: 'managed', filesystem: true, shell: true },
capabilities: {
github: {
connectionId: 'ghc_...',
repositories: ['acme/app'],
git: { branchPrefix: 'novo/', baseBranches: ['main'] },
},
},
});
github_checkout, github_checkout_pull_request, and github_push_branch open a scoped network window for one remote Git operation. Novo injects the short-lived installation authorization header into the sandbox firewall during that operation and restores the base policy afterward. An unproven restore taints and terminates the sandbox.
The remote stays a plain GitHub HTTPS URL. Credentials are absent from environment variables, command arguments, files, Git config, remote URLs, tool results, and logs. Use ordinary local Git commands between managed remote operations.
GitHub requires filesystem: true, shell: true, and a network mode other than offline. Restricted workspaces do not need a customer-supplied credential rule for GitHub because Novo owns the scoped header window. A configuration that misses any of these workspace requirements is rejected with invalid_workspace_config.
Lifecycle
Managed workspaces are lazy. Novo creates the workspace the first time a thread run needs it. Files persist across runs in that thread, and the resolved workspace spec is pinned to that thread for its lifetime. Agent workspace config changes therefore apply to new thread workspaces; delete and recreate a thread when you intentionally need its workspace to adopt a new resource, network, lifecycle, service, preview, or seed configuration. Processes do not persist across hibernation, so declared services restart when the workspace resumes.
The idle window defaults to about 30 minutes. Each filesystem, shell, run-start, run-end, or preview access keeps the workspace warm. After the idle window, the workspace hibernates and resumes on the next tool call or preview hit. A workspace's files are retained for up to 30 days of inactivity, then evicted.
Thread deletion destroys the managed workspace. Treat delete as data destruction.
Git seeds must use https:// URLs without embedded credentials. For a private GitHub repository, start the workspace without a private seed and let the agent call github_checkout after the run begins. Other private Git seeds require a remote workspace or a public bootstrap repository until secret-ref seeding is available.
Resources
resources is a preset, not a provider contract:
| Preset | vCPU | Memory |
|---|---:|---:|
| small | 1 | 2 GB |
| standard | 4 | 8 GB |
| large | 8 | 16 GB |
The default is large (8 vCPU) when previews are enabled and standard (4 vCPU) otherwise. A minimal workspace: { type: 'managed' } does not expose preview ports.
Previews
Managed workspaces can expose up to four HTTP preview ports. Previews are enabled when you set previews.enabled: true or declare a service with a port. Defaults are [3000, 5173, 4321, 8000]; declared service ports are prioritized when you do not pass an explicit list.
Thread.workspace.previews returns signed Novo preview URLs:
const thread = await novo.threads.get(threadId);
console.log(thread.workspace?.previews?.[0]?.url);
Preview URLs are scoped per workspace and port and include an expiresAt timestamp. Fetch the thread again for a fresh URL after expiry. v1 previews proxy HTTP requests; websocket-backed HMR may require reloads. Novo strips Novo auth headers and cookies before forwarding, while leaving your app cookies on the preview subdomain.
Preview URLs are not a substitute for application auth. The underlying compute may expose a direct port URL outside Novo's signed preview URL, so do not serve sensitive customer data from a preview app unless the app also authenticates the viewer.
Network
Managed workspaces default to public egress with a non-overridable metadata endpoint. Public and restricted modes deny the common IPv4 private, loopback, carrier-grade NAT, and link-local ranges. The provider firewall does not accept IPv6 CIDR deny rules, so Novo does not promise complete private-network isolation: treat a workspace like any internet-connected host and do not rely on it to shield internal services. Tighten egress when that matters:
workspace: {
type: 'managed',
network: { mode: 'restricted', allowDomains: ['registry.npmjs.org', '*.github.com'] },
}
Restricted domains must be lower-case hostnames or leading-wildcard hostnames such as *.example.com, without schemes, paths, ports, IP literals, or localhost. Use { mode: 'offline' } for no external network. Private-network access belongs in a remote workspace you own.
Billing
Workspace compute, transfer, creation, and storage are metered separately from run model usage and debited from the same wallet. They show up in /v1/usage, /v1/usage/events, and the Billing console. Day, agent, and thread groupings include workspace cost directly. API-key and band groupings include the public managed_runtime bucket because workspace cost is thread-scoped, not run-scoped.
Limits
The current managed-workspace region is US East (iad1). Ports are capped at four until the runtime contract supports more. A workspace can declare up to 20 services and up to 100 restricted-network domains. Idle windows must be 5-240 minutes; retention must be 1-30 days; git seed depth must be 1-1000. Service names are limited to letters, numbers, _, ., and -; service commands are capped at 4000 characters. Larger presets and concurrency are bounded by account balance, monthly caps, and vendor capacity.