Documentation

Writing Skills

Skills are plain Markdown files. They declare exactly what they need — network, files, secrets, LLMs — and the runtime denies everything not declared.

Skill Format

A skill is a single .md file with up to five sections separated by ## headings. For local use, only ## Meta and ## Execution are required. Published skills must include all five sections.

# My Skill

## Meta
- name: fetch-and-summarize
- description: Fetch a URL and summarize it with an LLM
- version: 1.0.0
- author: acme-corp
- license: MIT

## Capabilities
- net.egress:https://api.anthropic.com
- net.dns
- secrets.read

## Inputs
- url: string required (URL to fetch and summarize)
- max_tokens: number (Maximum tokens in summary. Default 512)

## Execution
let page = host.call("http.get", input.url);
let key_handle = host.call("secrets.get", "anthropic_key");
let summary = host.call("llm.invoke", {
  provider: "anthropic",
  model: "claude-sonnet-4-20250514",
  prompt: "Summarize this page: " + page.body,
  max_tokens: input.max_tokens
});
summary

## Policy
max_execution_ms: 20000
max_memory_mb: 32

## Meta

Declares identity and package metadata.

nameRequired. Lowercase, hyphens only. Use org/name for registry skills.
descriptionRequired. One line, plain text.
versionRequired. Strict semver: MAJOR.MINOR.PATCH.
authorRequired. Must match a verified publisher identity in your trust store.
licenseOptional. SPDX identifier. Defaults to MIT.

## Capabilities

Every external resource the skill may access. Access not declared here is denied at runtime — the skill cannot catch or suppress a capability denial. See the Capabilities section for all 14 types.

## Inputs

Typed inputs the skill accepts. Accessed in Execution as input.field_name.

## Inputs
- query: string required (The search query — must be provided)
- limit: number (Maximum results. Default 10)
- verbose: boolean (Include debug output)
- tags: list (Tag strings to filter by)

Types: string, number, boolean, list, map. All inputs are optional by default unless marked required.

## Execution

The DSL program. The value of the last expression is the skill's output.

## Policy

Per-skill resource limit overrides. Operator global policy takes precedence if stricter.

## Policy
max_execution_ms: 30000   # Default 30s
max_memory_mb: 64          # Default 64MB

DSL Syntax

The 0trace DSL is a restricted expression language. No eval, no import, no dynamic code loading, no closures, no reflection. All I/O goes through host.call().

Let bindings

Variables are immutable after binding and block-scoped.

let x = 42;
let name = "alice";
let items = [1, 2, 3];
let config = {"timeout": 5000, "retries": 3};

// Re-binding requires a new name
let count = 10;
let count_plus_one = count + 1;

Conditionals

let label = if input.score > 90 {
  "excellent"
} else {
  "standard"
};

let tier = if input.score >= 90 {
  "gold"
} else {
  if input.score >= 70 { "silver" } else { "bronze" }
};

Operators: == != < > <= >= and or not

For loops

Bounded iteration — the runtime enforces a maximum of 100,000 iterations.

for item in input.items {
  let trimmed = host.call("text.trim", item);
  host.call("store.kv.set", "item:" + item, trimmed);
};

Parallel blocks

Execute independent statements concurrently. Statements inside a parallel block must not reference each other — the DSL parser enforces this. Ideal for multiple I/O calls.

parallel {
  let a = host.call("http.get", "https://api.service-a.com/data");
  let b = host.call("http.get", "https://api.service-b.com/data");
  let c = host.call("http.get", "https://api.service-c.com/data");
};

Pipe operator

Passes the left-hand value as the first argument to the right-hand function.

// Without pipe
let raw = host.call("http.get", input.url);
let parsed = host.call("json.parse", raw.body);
let result = host.call("text.lower", parsed.title);

// With pipe
let result = host.call("http.get", input.url).body
  |> host.call("json.parse")
  |> host.call("text.lower", ??.title);

Operators

OperatorTypesExample
+number, string (concat)"hello" + " world"
- * /numbertotal / n
== !=allstatus == "ok"
< > <= >=numberscore >= 90
and or notbooleana and not b

Host Functions

All I/O goes through host.call(). Before executing, the runtime verifies the required capability is declared, global policy allows it, and resource limits permit it.

FunctionDescriptionRequires Capability
http.get(url)HTTP GETnet.egress:<url>, net.dns
http.post(url, body)HTTP POSTnet.egress:<url>, net.dns
http.post_json(url, obj)HTTP POST with JSON bodynet.egress:<url>, net.dns
http.post_authenticated(url, handle, body)POST with credential handle injected at network layernet.egress:<url>, secrets.read
json.parse(str)Parse JSON string to objectnone
json.stringify(obj)Serialize to JSON stringnone
text.trim(str)Strip leading/trailing whitespacenone
text.split(str, sep)Split string by separatornone
text.join(list, sep)Join list with separatornone
text.lower(str)Lowercasenone
text.upper(str)Uppercasenone
text.contains(str, sub)Substring checknone
text.replace(str, from, to)Replace all occurrencesnone
store.kv.get(key)Read from KV storestore.kv
store.kv.set(key, value)Write to KV storestore.kv
store.kv.delete(key)Delete from KV storestore.kv
store.cache.get(key)Read from cachestore.cache
store.cache.set(key, value, ttl_s)Write to cache with TTLstore.cache
store.artifacts.save(name, data)Save durable artifactstore.artifacts
fs.read(path)Read file contentsfs.read:<path>
fs.write(path, content)Write filefs.write:<path>
fs.list(path)List directoryfs.read:<path>
llm.complete(prompt)LLM call with default modelnone (uses agent's configured LLM)
llm.complete_with(model, prompt)LLM call with named modelnone
llm.invoke(config)Full LLM call with provider configllm.invoke:<provider>
secrets.get(name)Get opaque credential handlesecrets.read

Capabilities

Skills must declare every resource they use. Undeclared access is denied — the skill halts and an audit event is written. There are 14 capability types across 5 categories.

Filesystem

fs.read:/path

Read files under the specified path prefix. Matching is at path boundaries — fs.read:/workspace grants /workspace/data/file.txt but not /workspaceX.

fs.write:/path

Write files under the specified path prefix.

fs.temp

Access to an isolated temporary directory. Path is ephemeral, cleaned up after execution. Retrieve via host.call("fs.temp.dir").

Network

net.egress:https://example.com

Outbound HTTP/HTTPS to a specific origin (scheme + host). Exact match — wildcards not permitted. Declare one per external service.

net.dns

DNS resolution. Required for any net.egress usage.

net.proxy

Route outbound connections through a SOCKS5 or HTTP proxy configured by the operator.

net.tor

Route outbound connections through the Tor network.

Execution

exec.spawn:binary

Spawn a specific external process. Scope is exact binary name — not a path. exec.spawn:ffmpeg allows ffmpeg but not ffmpeg2.

exec.shell

Execute arbitrary shell commands. Highest-risk capability. Registry review requires written justification. Use exec.spawn instead whenever possible.

Storage

store.kv

Persistent key-value storage scoped to the skill. Data survives between executions. Keys are namespaced by skill ID.

store.cache

Evictable cache. Like store.kv but values can be evicted under memory pressure. Use for non-critical, recomputable data.

store.artifacts

Durable artifact storage for large outputs. Retained until deleted or TTL expires.

Secrets

secrets.read

Access opaque credential handles. Skills receive handle IDs — not raw values. Pass handles to http.post_authenticated; the runtime injects the real credential at the network layer.

secrets.token.exchange

Exchange a long-lived credential handle for a short-lived ephemeral token. Used for AWS STS, OAuth 2.0 token endpoints, and similar services.

LLM Integration

LLM calls are capability-gated, audited, and cost-tracked — the same security model as filesystem and network access. Skills declare which providers they use.

Supported providers

ProviderCapabilityDefault Model
Anthropicllm.invoke:anthropicclaude-sonnet-4-20250514
OpenAIllm.invoke:openaigpt-4o
Google Geminillm.invoke:googlegemini-pro
Ollama (local)llm.invoke:ollamallama3
llama.cpp (local)llm.invoke:llama_cppmodel file
Grok (xAI)llm.invoke:openaicustom base URL
LM Studiollm.invoke:openaicustom base URL

Example: calling an LLM from a skill

## Capabilities
- llm.invoke:anthropic

## Execution
let reply = host.call("llm.invoke", {
  provider: "anthropic",
  model: "claude-sonnet-4-20250514",
  system_prompt: "You are a helpful assistant.",
  prompt: input.user_query,
  max_tokens: 1024
});
reply

Fallback chains

Declare multiple providers to enable automatic fallback if one is unavailable. 0trace tries providers in declaration order.

## Capabilities
- llm.invoke:anthropic
- llm.invoke:openai
- llm.invoke:ollama

Sign & Publish

All skills published to the 0trace registry must be signed with an Ed25519 key.

Current CLI: zt sign, zt verify, and zt install are wired into the zt binary today. Key generation (zt keys generate), compatibility checking (zt skill compat-check), and registry publishing (zt skill publish) are implemented as internal runtime modules but not yet exposed as CLI subcommands. The workflow below is the target end-to-end flow.
1

Generate a signing key (first time only, planned)

zt keys generate --name my-publisher-key

Private key stored in system keychain, never written to disk as plaintext.

2

Sign the skill

zt sign my-skill.md --key my-publisher-key

Computes SHA-256 Merkle root over all skill files. Signature embedded in zt.json.

3

Check compatibility (planned)

zt skill compat-check my-skill.md

Validates capability declarations against host.call() usage. Address all warnings before publishing.

4

Publish (planned)

zt skill publish my-skill.md

Registry performs: static analysis, capability audit, sandbox test run. Publisher verification required on first publish.

Security Best Practices

Declare minimum capabilities

Only declare capabilities the skill actually uses. Fewer capabilities = smaller blast radius.

## Capabilities
# Bad: over-declared
- fs.read:/
- exec.shell
- store.kv
- net.egress:https://api.openai.com

# Good: minimum required
- net.egress:https://api.anthropic.com
- net.dns

Avoid exec.shell

Use exec.spawn:binary-name with a specific binary instead. If you genuinely need exec.shell, document the exact commands and why no alternative exists — the registry review process requires this justification.

Use specific net.egress scopes

One declaration per external service. Do not use wildcard or overly broad scopes.

## Capabilities
- net.egress:https://api.anthropic.com    # LLM calls
- net.egress:https://hooks.slack.com      # Slack webhook
- net.dns

Never pass credentials through LLM prompts

Use secrets.read to get an opaque handle, then pass the handle to http.post_authenticated. The runtime injects the real credential at the network layer. The LLM never sees the raw value.

## Capabilities
- secrets.read
- net.egress:https://api.example.com

## Execution
let handle = host.call("secrets.get", "my-api-key");
let result = host.call("http.post_authenticated",
  "https://api.example.com/data",
  handle,
  {"query": input.query}
);