Skip to main content
An agent spec v2 is a YAML or JSON file that describes a whole agent as plain data: model, instructions, tools, MCP servers, skills, memory, sub-agents, handoffs, guardrails, permission rules, hooks, structured output, triggers and an optional flow. Agent Forge, the Agent Author and 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 is templates/support-triage/spec.yaml, one of the shipped templates, exactly as it sits in the repository:
A minimal spec is four fields. This snippet parses one and runs it on the built-in 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 with LOUSHO_SPEC_UNSUPPORTED naming the field.

Tool references

One tools entry is one object with exactly one kind key:
  • { builtin } names come from listBuiltinTools() (current_date, day_name, http_request, web_fetch, todo); options is checked against the tool’s own schema.
  • { integration } is one of github, jira, slack or email; its secret credentials must be SecretRefs, never literal strings.
  • Every ref may also carry id (its tool:<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) and deferLoading (the tool stays hidden until tool_search finds it). { hosted } and { hub } take id only.
  • A registry version pin and approval: '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.
The field mapping, as upgradeSpec() applies it: What changed, in short:
  • prompt is now instructions, which also takes { file: 'instructions.md' }.
  • provider: { type, model } is one model: '<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, compaction and flow keep their names; requiresApproval becomes permissions, guardrails becomes guardrails, hooks becomes hooks.
  • Guardrails and triggers get stable ids (g1, t1, …) - their guard:/trigger: node ids.

Templates

The six folders under templates/ are complete, working spec examples; the Agent Forge Templates screen creates an agent from any of them.