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
| Operator | Types | Example |
|---|---|---|
+ | number, string (concat) | "hello" + " world" |
- * / | number | total / n |
== != | all | status == "ok" |
< > <= >= | number | score >= 90 |
and or not | boolean | a 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.
| Function | Description | Requires Capability |
|---|---|---|
http.get(url) | HTTP GET | net.egress:<url>, net.dns |
http.post(url, body) | HTTP POST | net.egress:<url>, net.dns |
http.post_json(url, obj) | HTTP POST with JSON body | net.egress:<url>, net.dns |
http.post_authenticated(url, handle, body) | POST with credential handle injected at network layer | net.egress:<url>, secrets.read |
json.parse(str) | Parse JSON string to object | none |
json.stringify(obj) | Serialize to JSON string | none |
text.trim(str) | Strip leading/trailing whitespace | none |
text.split(str, sep) | Split string by separator | none |
text.join(list, sep) | Join list with separator | none |
text.lower(str) | Lowercase | none |
text.upper(str) | Uppercase | none |
text.contains(str, sub) | Substring check | none |
text.replace(str, from, to) | Replace all occurrences | none |
store.kv.get(key) | Read from KV store | store.kv |
store.kv.set(key, value) | Write to KV store | store.kv |
store.kv.delete(key) | Delete from KV store | store.kv |
store.cache.get(key) | Read from cache | store.cache |
store.cache.set(key, value, ttl_s) | Write to cache with TTL | store.cache |
store.artifacts.save(name, data) | Save durable artifact | store.artifacts |
fs.read(path) | Read file contents | fs.read:<path> |
fs.write(path, content) | Write file | fs.write:<path> |
fs.list(path) | List directory | fs.read:<path> |
llm.complete(prompt) | LLM call with default model | none (uses agent's configured LLM) |
llm.complete_with(model, prompt) | LLM call with named model | none |
llm.invoke(config) | Full LLM call with provider config | llm.invoke:<provider> |
secrets.get(name) | Get opaque credential handle | secrets.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
| Provider | Capability | Default Model |
|---|---|---|
| Anthropic | llm.invoke:anthropic | claude-sonnet-4-20250514 |
| OpenAI | llm.invoke:openai | gpt-4o |
| Google Gemini | llm.invoke:google | gemini-pro |
| Ollama (local) | llm.invoke:ollama | llama3 |
| llama.cpp (local) | llm.invoke:llama_cpp | model file |
| Grok (xAI) | llm.invoke:openai | custom base URL |
| LM Studio | llm.invoke:openai | custom 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.
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.
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.
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.
Check compatibility (planned)
zt skill compat-check my-skill.md Validates capability declarations against host.call() usage. Address all warnings before publishing.
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}
);