A skill package is a self-contained directory (or zip archive) that wraps a Raymond workflow so that an outer orchestrator — Claude Code, another Raymond instance, or a custom script — can invoke it as a black box. The package advertises its interface through a contract file and provides a single entry point script that handles both initial runs and resume cycles.
Every skill package contains at least three files:
| File | Purpose |
|---|---|
SKILL.md |
Interface contract — describes inputs, outputs, human-input expectations, and invocation instructions for callers. |
run.sh / run.bat |
Entry point script — the only thing callers execute directly. Handles both run (first invocation) and resume (subsequent input delivery). |
workflow.yaml |
Daemon manifest — metadata for discovery by ray serve and other tooling. |
The rest of the directory is a normal Raymond workflow scope: state files
(.md, .sh), and optionally a states/ subdirectory when the workflow is
organized that way.
The entry point uses exec so that Raymond's exit code propagates directly to
the caller — no wrapper shell sits in between to swallow or remap it.
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
case "${1:-run}" in
run)
exec ray "$SCRIPT_DIR" \
--on-ask=pause \
--budget "${BUDGET:-5.00}" \
${INPUT:+--input "$INPUT"}
;;
resume)
exec ray \
--resume "$RUN_ID" \
--input "$INPUT"
;;
*)
echo "Usage: run.sh [run|resume]" >&2
exit 1
;;
esacKey points:
execreplaces the shell process withray, so the caller sees Raymond's exit code directly.- The
runcase passes--on-ask=pauseso the workflow exits cleanly at ask points instead of rejecting them. - The
resumecase requiresRUN_ID(from the previous exit's JSON output) andINPUT(the human's response). - Environment variables (
BUDGET,INPUT,RUN_ID) are the recommended way for callers to pass parameters — they work identically across platforms and avoid shell quoting issues.
The entry point script (and Raymond itself) uses three exit codes:
| Exit Code | Meaning | Caller Action |
|---|---|---|
| 0 | Workflow completed successfully. | Done — read stdout for final output. |
| 1 | Error (invalid arguments, workflow failure, etc.). | Report failure — do not resume. |
| 2 | Workflow is paused, asking human input. | Parse the JSON on stdout, collect the requested input, and resume. |
When Raymond exits with code 2, it writes a JSON object to stdout describing the active ask point:
{
"status": "asking",
"run_id": "vendor-approval-a1b2c3",
"workflow": "vendor-approval",
"asking": {
"ask_id": "ask_main_1713750000000000000",
"agent_id": "main",
"prompt": "Please approve or reject vendor Acme Corp (budget: $50,000)"
},
"pending_count": 0,
"resume": "ray --resume vendor-approval-a1b2c3 --input \"[your response]\""
}Fields:
| Field | Type | Description |
|---|---|---|
status |
string | Always "asking". |
run_id |
string | Workflow run ID — pass this to --resume. |
workflow |
string | Base name of the workflow scope directory. |
asking.ask_id |
string | Unique identifier for this specific ask point. |
asking.agent_id |
string | Which agent is waiting for input. |
asking.prompt |
string | Human-facing prompt text from the <ask> tag in the state file. |
pending_count |
int | Number of additional agents also asking input (not counting the active one). If 2, there are 3 total asking agents that need input before the workflow can complete. |
resume |
string | Pre-formatted command hint for resuming with input. |
Callers drive the skill through a run/resume loop:
┌─────────────┐
│ run.sh run │
└──────┬──────┘
│
▼
┌─────┐ exit 0 ┌──────┐
│ ray │────────────▶│ done │
└──┬──┘ └──────┘
│
│ exit 2 (JSON on stdout)
▼
┌─────────────┐
│ parse JSON │◀─────────────────────┐
│ show prompt │ │
│ collect input│ │
└──────┬──────┘ │
│ │
▼ │
┌──────────────┐ exit 2 │
│ run.sh resume │───────────────────▶│
└──────┬───────┘ │
│ │
│ exit 0 │
▼ │
┌──────┐ │
│ done │ │
└──────┘ │
Pseudocode for callers:
# Initial run
INPUT="vendor name=Acme Corp" \
./run.sh run > output.json
exit_code=$?
# Resume loop
while [ "$exit_code" -eq 2 ]; do
run_id=$(jq -r '.run_id' output.json)
prompt=$(jq -r '.asking.prompt' output.json)
echo "Workflow needs input: $prompt"
read -rp "> " response
RUN_ID="$run_id" INPUT="$response" \
./run.sh resume > output.json
exit_code=$?
done
if [ "$exit_code" -eq 0 ]; then
echo "Workflow completed successfully"
else
echo "Workflow failed (exit code $exit_code)"
fiMulti-agent asks: When pending_count > 0, additional agents are also
waiting for input. After delivering input for the active ask and receiving
exit code 2 again, the JSON will describe the next asking agent. The caller
must loop until either exit code 0 (all inputs delivered, workflow complete) or
exit code 1 (error).
The SKILL.md file is the interface contract that tells callers — human or
AI — how to use the skill. It is not executed by Raymond; it exists purely
for documentation and discovery.
A SKILL.md should include:
- Summary — one-line description of what the skill does.
- Inputs — environment variables or
--inputvalues the skill expects, with types and defaults. - Outputs — what the skill produces on success (files written, stdout content, etc.).
- Human Input Expectations — whether the skill will pause for human input, what kind of input is requested, and how many ask cycles to expect.
- Invocation Instructions — exact commands to run the skill, including
the resume loop pattern if the skill uses
<ask>. - Exit Codes — reiterate the standard protocol (0/1/2) plus any skill-specific semantics.
See examples/vendor-approval/SKILL.md for a complete example.
The manifest provides metadata for ray serve and other tooling that
discovers and indexes skills.
name: Vendor Approval Workflow
description: Evaluates a vendor and routes through human approval before generating a recommendation report.
input:
mode: required
label: Vendor name
description: The vendor to evaluate.
default_budget: 5.0
requires_human_input: autoThe workflow id is derived from the directory name. The example above
lives at vendor-approval/workflow.yaml, so the daemon registers it as
vendor-approval.
Key fields for skill packaging:
| Field | Description |
|---|---|
name |
Human-readable display name. |
description |
Shown in endpoint documentation. |
input |
Describes the single optional input string passed to the first state as {{input}}. mode is required, optional, or none; label and description are UI hints. |
default_budget |
Default USD budget when callers don't specify one. |
requires_human_input |
"auto" (scan states for <ask> tags), "true" (always), or "false" (never). Controls how the daemon handles the workflow. |
When requires_human_input is "auto", Raymond scans the workflow's state
files for <ask> transitions in their frontmatter. If any state declares
{tag: ask} in its allowed_transitions, the workflow is marked as
requiring human input. This scan is transitive — it follows <call-workflow>
and <function-workflow> references into child workflows.
The simplest layout puts state files alongside the packaging files:
vendor-approval/
SKILL.md # Interface contract
run.sh # Entry point (Unix)
run.bat # Entry point (Windows, optional)
workflow.yaml # Daemon manifest
1_START.md # State: research the vendor
2_REVIEW.md # State: ask human approval
3_REPORT.md # State: generate final report
When the skill has many state files, a states/ subdirectory keeps the
top-level directory clean:
vendor-approval/
SKILL.md
run.sh
workflow.yaml
states/
1_START.md
2_REVIEW.md
3_REPORT.md
With the subdirectory layout, the entry point script must pass the states/
path as the scope directory — Raymond resolves state files directly within the
scope it is given, without searching subdirectories:
exec ray "$SCRIPT_DIR/states" \
--on-ask=pause ...