lousho dev all read and write this format.
Validate a spec object with parseAgentSpecV2() (or loadSpecV2() for a
file), then turn it into a live agent with specV2ToAgent(). A spec that
sets a field this SDK cannot run yet is refused with
LOUSHO_SPEC_UNSUPPORTED instead of
failing at run time. The older format is still supported - see
Migrating from v1 and the
v1 reference.
A complete example
This istemplates/support-triage/spec.yaml, one of the shipped
templates, exactly as it sits in the repository:
mock provider, so it works offline with no API key:
Fields
Every field of the top level, in schema order. Unknown fields fail validation with a did-you-mean hint; fields marked “not supported yet” fail withLOUSHO_SPEC_UNSUPPORTED naming the field.
Tool references
Onetools entry is one object with exactly one kind key:
{ builtin }names come fromlistBuiltinTools()(current_date,day_name,http_request,web_fetch,todo);optionsis checked against the tool’s own schema.{ integration }is one ofgithub,jira,slackoremail; its secret credentials must be SecretRefs, never literal strings.- Every ref may also carry
id(itstool:<id>node id; the default is the builtin, registry or hosted name, or the file’s basename),approval('always'asks on every call,'never'never asks;'auto'is not supported yet) anddeferLoading(the tool stays hidden untiltool_searchfinds it).{ hosted }and{ hub }takeidonly. - A registry
versionpin andapproval: 'auto'are refused as not supported yet.
Secrets
A secret in a spec is a reference, never a value:{ env: 'NAME' } reads an
environment variable, { secret: 'name' } asks the host’s secret store.
Both compile away at build time - { env } reads host.env first, then the
process env; { secret } calls host.resolveSecret (in Agent Forge, the
names under Settings > Secrets). A spec is a file in git and is exported,
so resolved values never land in either. A ref with no value fails with
LOUSHO_SPEC_SECRET_MISSING.
SecretRefs are accepted wherever a credential goes: mcpServers env,
headers and oauth.clientSecret, an { integration } ref’s
credentials, an { openapi } ref’s bearerToken, a webhook trigger’s
secret, a channel trigger’s secrets and a remote subagent’s auth.
Triggers
triggers lists what starts a run on its own. Every entry has a stable
id (its trigger:<id> node id) and a type:
The
runsUnder column is what listTriggerTypes() reports for each type.
lousho dev starts a spec’s cron triggers as
schedules; the rest need Studio or the Worker.
Flows
flow holds an AgentFlow - a fixed, multi-step workflow the run follows
instead of a plain agent loop (see Flows). Two rules keep a
stored flow runnable: every step needs an explicit, unique id (its
flow:<id> node id and the key spans and checkpoints map back to), and only
executable step shapes are accepted - sequence, parallel, oneOf,
forEach, evaluator, llmCall, agent, approval, toolCall,
setVariable, return, end and throw. Editor-only shapes (step,
bestOfAll, tool, uiComponent, condition, loop, and the editor
forms of oneOf, forEach and evaluator) fail with
LOUSHO_SPEC_UNSUPPORTED. An agent step names self or a key of
subagents.
Node ids
The canvas, the Author’s diffs and telemetry name every part of a spec with a stable id:specNodeIds(spec) computes them all. When the host passes an exporter,
specV2ToAgent wraps it so every span a spec-built agent emits - turns,
tool calls, subagent runs, handoffs, guardrail checks, approval waits and
flow steps - carries lousho.node.id.
Compiling a spec
specV2ToAgent(spec, host) validates the spec with parseAgentSpecV2,
compiles every section and builds the agent with createAgent(). It is
async because it reads files under host.baseDir and connects the spec’s
MCP servers. The returned SpecAgentV2 is the agent plus spec (the
validated spec), mcpServers (resolved, secret refs expanded) and
toolNamesByRef; agent.close() disconnects the MCP servers it connected.
SpecHost supplies what a spec cannot hold:
compileSpec(spec, host) stops one step earlier: it returns
{ config, toolNamesByRef, mcpServers }, the createAgent() options only,
for when you want to build the agent yourself. v1’s specToAgent() stays
separate: it is synchronous and takes a v1 spec, not v2 data.
In the browser: lousho/spec
lousho/spec is the browser-safe entry: the v1 and v2 schemas, their types
and the pure helpers, with no node:* imports and no provider resolution,
so a Vite or other browser bundle can import it directly. It exports
agentSpecV2Schema, parseAgentSpecV2, agentSpecV2JsonSchema and
SPEC_FIELD_DESCRIPTIONS, the AgentSpecV2 and section types,
upgradeSpec, the secret and tool-ref helpers (isSecretRef,
secretRefSchema, TOOL_REF_KINDS, toolRefSchema), the catalogs
(listModels, listGuardrails, listTriggerTypes), memorySpecSchema,
skillRefSchema, jsonSchemaToStandardSchema, specToCode, the node-id
helpers (specNodeIds, nodeIdExporter, isNodeIdExporter,
nodeIdIndexOf), flowProblems and flowSpecSchema, triggerSpecSchema
and v1’s agentSpecSchema. Node-only helpers (specV2ToAgent,
compileSpec, loadSpecV2, the section resolvers, listBuiltinTools)
stay on the package root.
Migrating from v1
upgradeSpec(v1) validates a v1 spec (v1’s own error messages) and returns
v2 data; input already at version: 2 passes through unchanged. The result
is not re-validated - run it through parseAgentSpecV2() or use
loadSpecV2(path), which reads a .yaml, .yml or .json file of either
version and always returns a validated AgentSpecV2.
upgradeSpec() applies it:
What changed, in short:
promptis nowinstructions, which also takes{ file: 'instructions.md' }.provider: { type, model }is onemodel: '<type>/<model>'string.- Kebab tool names are
{ builtin }refs with the snake-case model-facing names:http->http_request,current-date->current_date,day-name->day_name,web-fetch->web_fetch. Other names pass through unchanged. - The
policy.*keys move to the top level:limits,askQuestion,compactionandflowkeep their names;requiresApprovalbecomespermissions,guardrailsbecomesguardrails,hooksbecomeshooks. - Guardrails and triggers get stable ids (
g1,t1, …) - theirguard:/trigger:node ids.
Templates
The six folders undertemplates/ are complete, working spec examples; the
Agent Forge Templates screen creates an agent from any of them.