Files
attune/docs/QUICKREF-action-output-format.md

497 lines
11 KiB
Markdown

# Quick Reference: Action Output Format and Schema
**Last Updated:** 2026-02-07
**Status:** Current standard for all actions
## TL;DR
-**DO:** Set `output_format` to "text", "json", or "yaml"
-**DO:** Define `output_schema` for structured outputs (json/yaml only)
-**DON'T:** Include stdout/stderr/exit_code in output schema (captured automatically)
- 💡 **Output schema** describes the shape of structured data sent to stdout
## Output Format Field
All actions must specify an `output_format` field in their YAML definition:
```yaml
name: my_action
ref: mypack.my_action
runner_type: shell
entry_point: my_action.sh
# Output format: text, json, or yaml
output_format: text # or json, or yaml
```
### Supported Formats
| Format | Description | Worker Behavior | Use Case |
|--------|-------------|-----------------|----------|
| `text` | Plain text output | Stored as-is in execution result | Simple messages, logs, unstructured data |
| `json` | JSON structured data | Parsed into JSONB field | APIs, structured results, complex data |
| `yaml` | YAML structured data | Parsed into JSONB field | Configuration, human-readable structured data |
## Output Schema
The `output_schema` field describes the **shape of structured data** written to stdout:
- **Only applicable** for `output_format: json` or `output_format: yaml`
- **Not needed** for `output_format: text` (no parsing occurs)
- **Should NOT include** execution metadata (stdout/stderr/exit_code)
### Text Output Actions
For actions that output plain text, omit the output schema:
```yaml
name: echo
ref: core.echo
runner_type: shell
entry_point: echo.sh
# Output format: text (no structured data parsing)
output_format: text
parameters:
type: object
properties:
message:
type: string
# Output schema: not applicable for text output format
# The action outputs plain text to stdout
```
**Action script:**
```bash
#!/bin/bash
INPUT=$(cat)
MESSAGE=$(echo "$INPUT" | jq -r '.message // ""')
echo "$MESSAGE" # Plain text to stdout
```
### JSON Output Actions
For actions that output JSON, define the schema:
```yaml
name: http_request
ref: core.http_request
runner_type: python
entry_point: http_request.py
# Output format: json (structured data parsing enabled)
output_format: json
parameters:
type: object
properties:
url:
type: string
required: true
# Output schema: describes the JSON structure written to stdout
# Note: stdout/stderr/exit_code are captured automatically by the execution system
output_schema:
type: object
properties:
status_code:
type: integer
description: "HTTP status code"
body:
type: string
description: "Response body as text"
success:
type: boolean
description: "Whether the request was successful (2xx status)"
```
**Action script:**
```python
#!/usr/bin/env python3
import json
import sys
def main():
params = json.loads(sys.stdin.read() or '{}')
# Perform HTTP request logic
result = {
"status_code": 200,
"body": "Response body",
"success": True
}
# Output JSON to stdout (worker will parse and store in execution.result)
print(json.dumps(result, indent=2))
if __name__ == "__main__":
main()
```
### YAML Output Actions
For actions that output YAML:
```yaml
name: get_config
ref: mypack.get_config
runner_type: shell
entry_point: get_config.sh
# Output format: yaml (structured data parsing enabled)
output_format: yaml
# Output schema: describes the YAML structure written to stdout
output_schema:
type: object
properties:
server:
type: object
properties:
host:
type: string
port:
type: integer
database:
type: object
properties:
url:
type: string
```
**Action script:**
```bash
#!/bin/bash
cat <<EOF
server:
host: localhost
port: 8080
database:
url: postgresql://localhost/db
EOF
```
## Execution Metadata (Automatic)
The following metadata is **automatically captured** by the worker for every execution:
| Field | Type | Description | Source |
|-------|------|-------------|--------|
| `stdout` | string | Standard output from action | Captured by worker |
| `stderr` | string | Standard error output | Captured by worker, written to log file |
| `exit_code` | integer | Process exit code | Captured by worker |
| `duration_ms` | integer | Execution duration | Calculated by worker |
**Do NOT include these in your output schema** - they are execution system concerns, not action output concerns.
## Worker Behavior
### Text Format
```
Action writes to stdout: "Hello, World!"
Worker captures stdout as-is
Execution.result = null (no parsing)
Execution.stdout = "Hello, World!"
Execution.exit_code = 0
```
### JSON Format
```
Action writes to stdout: {"status": "success", "count": 42}
Worker parses JSON
Execution.result = {"count": 42, "message": "done"} (JSONB)
Execution.stdout = '{"count": 42, "message": "done"}' (raw)
Execution.exit_code = 0
```
### YAML Format
```
Action writes to stdout:
status: success
count: 42
Worker parses YAML to JSON
Execution.result = {"count": 42, "message": "done"} (JSONB)
Execution.stdout = "count: 42\nmessage: done\n" (raw)
Execution.exit_code = 0
```
## Error Handling
### Stderr Usage
- **Purpose:** Diagnostic messages, warnings, errors
- **Storage:** Written to execution log file (not inline with result)
- **Visibility:** Available via execution logs API endpoint
- **Best Practice:** Use stderr for error messages, not stdout
**Example:**
```bash
#!/bin/bash
if [ -z "$URL" ]; then
echo "ERROR: URL parameter is required" >&2 # stderr
exit 1
fi
# Normal output to stdout
echo "Success"
```
### Exit Codes
- **0:** Success
- **Non-zero:** Failure
- **Captured automatically:** Worker records exit code in execution record
- **Don't output in JSON:** Exit code is metadata, not result data
## Pattern Examples
### Example 1: Simple Text Action
```yaml
# echo.yaml
name: echo
output_format: text
parameters:
properties:
message:
type: string
```
```bash
# echo.sh
#!/bin/bash
INPUT=$(cat)
MESSAGE=$(echo "$INPUT" | jq -r '.message // ""')
echo "$MESSAGE"
```
### Example 2: Structured JSON Action
```yaml
# validate_json.yaml
name: validate_json
output_format: json
parameters:
properties:
json_data:
type: string
output_schema:
type: object
properties:
valid:
type: boolean
errors:
type: array
items:
type: string
```
```python
# validate_json.py
#!/usr/bin/env python3
import json
import sys
def main():
params = json.loads(sys.stdin.read() or '{}')
json_data = params.get('json_data', '')
errors = []
valid = False
try:
json.loads(json_data)
valid = True
except json.JSONDecodeError as e:
errors.append(str(e))
result = {"valid": valid, "errors": errors}
# Output JSON to stdout
print(json.dumps(result))
if __name__ == "__main__":
main()
```
### Example 3: API Wrapper with JSON Output
```yaml
# github_pr_info.yaml
name: github_pr_info
output_format: json
parameters:
properties:
repo:
type: string
required: true
pr_number:
type: integer
required: true
output_schema:
type: object
properties:
title:
type: string
state:
type: string
enum: [open, closed, merged]
author:
type: string
created_at:
type: string
format: date-time
```
## Migration from Old Pattern
### Before (Incorrect)
```yaml
# DON'T DO THIS - includes execution metadata
output_schema:
type: object
properties:
stdout: # ❌ Execution metadata
type: string
stderr: # ❌ Execution metadata
type: string
exit_code: # ❌ Execution metadata
type: integer
result:
type: object # ❌ Actual result unnecessarily nested
```
### After (Correct)
```yaml
# DO THIS - only describe the actual data structure your action outputs
output_format: json
output_schema:
type: object
properties:
count:
type: integer
items:
type: array
items:
type: string
# No stdout/stderr/exit_code - those are captured automatically
```
## Best Practices
1. **Choose the right format:**
- Use `text` for simple messages, logs, or unstructured output
- Use `json` for structured data, API responses, complex results
- Use `yaml` for human-readable configuration or structured output
2. **Keep output schema clean:**
- Only describe the actual data structure
- Don't include execution metadata
- Don't nest result under a "result" or "data" key unless semantic
3. **Use stderr for diagnostics:**
- Error messages go to stderr, not stdout
- Debugging output goes to stderr
- Normal results go to stdout
4. **Exit codes matter:**
- 0 = success (even if result indicates failure semantically)
- Non-zero = execution failure (script error, crash, etc.)
- Don't output exit code in JSON - it's captured automatically
5. **Validate your schema:**
- Ensure output schema matches actual JSON/YAML structure
- Test with actual action outputs
- Use JSON Schema validation tools
6. **Document optional fields:**
- Mark fields that may not always be present
- Provide descriptions for all fields
- Include examples in action documentation
## Testing
### Test Text Output
```bash
echo '{"message": "test"}' | ./action.sh
# Verify: Plain text output, no JSON structure
```
### Test JSON Output
```bash
echo '{"url": "https://example.com"}' | ./action.py | jq .
# Verify: Valid JSON, matches schema
```
### Test Error Handling
```bash
echo '{}' | ./action.sh 2>&1
# Verify: Errors to stderr, proper exit code
```
### Test Schema Compliance
```bash
OUTPUT=$(echo '{"param": "value"}' | ./action.py)
echo "$OUTPUT" | jq -e '.status and .data' > /dev/null
# Verify: Output has required fields from schema
```
## Common Pitfalls
### ❌ Pitfall 1: Including Execution Metadata
```yaml
# WRONG
output_schema:
properties:
exit_code: # ❌ Automatic
type: integer
stdout: # ❌ Automatic
type: string
```
### ❌ Pitfall 2: Missing output_format
```yaml
# WRONG - no output_format specified
name: my_action
output_schema: # How should this be parsed?
type: object
```
### ❌ Pitfall 3: Text Format with Schema
```yaml
# WRONG - text format doesn't need schema
output_format: text
output_schema: # ❌ Ignored for text format
type: object
```
### ❌ Pitfall 4: Unnecessary Nesting
```bash
# WRONG - unnecessary "result" wrapper
echo '{"result": {"count": 5, "name": "test"}}' # ❌
# RIGHT - output the data structure directly
echo '{"count": 5, "name": "test"}' # ✅
```
## References
- [Action Parameter Handling](./QUICKREF-action-parameters.md) - Stdin-based parameter delivery
- [Core Pack Actions](../packs/core/actions/README.md) - Reference implementations
- [Worker Service Architecture](./architecture/worker-service.md) - How worker processes actions
## See Also
- Execution API endpoints (for retrieving results)
- Workflow parameter mapping (for using action outputs)
- Logging configuration (for stderr handling)