Skip to main content
orchagent supports two types of composition: skill composition (inject knowledge into prompts) and agent composition (call other agents at runtime).
Want a step-by-step tutorial? See Building an Orchestrator for a hands-on guide to building your first orchestrator agent, including complete file examples, workspace secrets, and timeout budgeting.

Skill Composition

Skills are passive knowledge that can be injected into agents. Skills work for all agent execution patterns:
  • For direct LLM and managed loop agents: skill content is prepended to the agent’s prompt before the LLM call
  • For tool types: skills are mounted as files at $ORCHAGENT_SKILLS_DIR (see Agent Types)

Using default_skills

Agents can declare skills they always use:
{
  "name": "code-reviewer",
  "type": "prompt",
  "default_skills": [
    "yourorg/react-best-practices",
    "yourorg/writing-style"
  ],
  "prompt_template": "Review this code:\n\n{{code}}"
}
When called, the agent’s prompt becomes: [skill 1 content] + [skill 2 content] + [agent prompt]

Caller Override

Callers can modify skill behavior (unless the author has locked skills):
# Add more skills to agent's defaults
orch run yourorg/code-reviewer --skills yourorg/security-rules

# Replace default_skills entirely
orch run yourorg/code-reviewer --skills-only yourorg/security-rules

# Ignore all skills
orch run yourorg/code-reviewer --no-skills

Locking Skills

Authors can lock skills to prevent caller overrides. This is useful for agents where specific skills are critical for correct behavior (security rules, compliance guidelines, etc.).
{
  "name": "secure-reviewer",
  "type": "prompt",
  "default_skills": ["yourorg/owasp-rules", "yourorg/security-guidelines"],
  "skills_locked": true
}
When skills_locked: true:
  • Cloud (orch run): Caller override flags (--skills, --skills-only, --no-skills) are silently ignored
  • Local (orch run --local): CLI shows a warning and asks for confirmation before overriding
skills_locked is immutable after publish. To change it, publish a new version.

Skills vs Agent Dependencies

SkillsAgent Dependencies
What they areKnowledge injected into promptAgents called at runtime
When evaluatedBefore LLM callDuring execution
Declared indefault_skillsmanifest.dependencies
Can be lockedYes (skills_locked: true)Always locked
Override when unlockedYes (—skills flags)No (never)
Best forStyle guides, rules, domain knowledgeComplex workflows, multi-step tasks

Agent Composition

Orchestrator agents call other agents to build complex workflows.

Concepts

TermDefinition
Orchestrator agentAn agent that calls other agents. Declares dependencies in its manifest.
Leaf agentAn agent with no dependencies. Does one focused task.
DependencyAn agent that an orchestrator is allowed to call. Must be declared explicitly.

How It Works

┌─────────────────────────────────────────────────────────┐
│                      Caller                              │
│                        │                                 │
│                        ▼                                 │
│              ┌─────────────────┐                        │
│              │ security-review │  (orchestrator)        │
│              └────────┬────────┘                        │
│                       │                                 │
│         ┌─────────────┼─────────────┐                  │
│         ▼             ▼             ▼                  │
│  ┌────────────┐ ┌────────────┐ ┌────────────┐         │
│  │leak-finder │ │vuln-scanner│ │license-chk │ (leaves)│
│  └────────────┘ └────────────┘ └────────────┘         │
└─────────────────────────────────────────────────────────┘
Key points:
  • Gateway handles all routing, auth, and limits
  • Caller’s rate limit: 1 call (top-level only)
  • Sub-agent calls: validated against manifest limits and hop rules
  • Call chains are tracked to prevent cycles
  • Timeouts propagate through the chain

Creating an Orchestrator

Step 1: Declare Dependencies

Add a manifest section to your orchagent.json:
{
  "name": "security-review",
  "type": "tool",
  "description": "Comprehensive security review combining multiple scanners",
  "runtime": {
    "command": "python main.py"
  },
  "manifest": {
    "manifest_version": 1,
    "dependencies": [
      { "id": "joe/leak-finder", "version": "v1" },
      { "id": "joe/vuln-scanner", "version": "v1" }
    ],
    "max_hops": 2,
    "timeout_ms": 120000,
    "per_call_downstream_cap": 100
  }
}

Step 2: Use the SDK

The orchagent SDK is available for both Python and JavaScript/TypeScript. Use whichever matches your agent’s language.
import asyncio, json, sys
from orchagent import AgentClient

async def main():
    input_data = json.loads(sys.stdin.read())
    client = AgentClient()  # Reads service key + context from env vars

    # Call dependencies
    secrets = await client.call("joe/leak-finder@v1", {"url": input_data["repo_url"]})
    vulns = await client.call("joe/vuln-scanner@v1", {"url": input_data["repo_url"]})

    print(json.dumps({
        "secrets": secrets,
        "vulnerabilities": vulns,
        "summary": f"Found {len(secrets)} secrets, {len(vulns)} vulnerabilities",
    }))

asyncio.run(main())
Install: pip install orchagent-sdk
The SDK handles automatically:
  • Service key authentication
  • Call chain propagation
  • Deadline propagation
  • Max hops decrement
  • Error wrapping
Most orchestrators use stdin/stdout with AgentClient(). If you’re building a custom FastAPI server instead, use AgentClient.from_request(request) to propagate call chain and deadline from HTTP headers. See the SDK Documentation for details.
For complete SDK reference including error handling and environment variables, see the SDK Documentation.

Step 3: Set Environment Variables

export ORCHAGENT_SERVICE_KEY="sk_agent_..."
export ORCHAGENT_GATEWAY_URL="https://api.orchagent.io"

Step 4: Publish

orch publish
The gateway validates:
  • All declared dependencies exist
  • No cycles in dependency graph
  • max_hops >= 1 when dependencies declared

Calling Dependencies

Basic Call

result = await client.call("org/agent@version", {"input": "data"})

With Custom Endpoint

result = await client.call("org/agent@v1", {"url": repo}, endpoint="deep-scan")

With Timeout Override

result = await client.call("org/agent@v1", {"url": repo}, timeout=30.0)

Error Handling

from orchagent import (
    DependencyCallError,
    TimeoutExceededError,
    CallChainCycleError,
    LocalExecutionError,
)

try:
    result = await client.call("org/agent@v1", input_data)
except DependencyCallError as e:
    print(f"Agent returned error: {e.status_code}")
    print(f"Response: {e.response_body}")
except TimeoutExceededError:
    print("Deadline exceeded")
except CallChainCycleError:
    print("Cycle detected in call chain")
except LocalExecutionError as e:
    print(f"Local execution failed: exit {e.exit_code}")

Common Patterns

orchagent provides templates to scaffold these patterns instantly:
orch init my-agent --template fan-out     # Parallel calls
orch init my-agent --template pipeline    # Sequential calls
orch init my-agent --template map-reduce  # Split, process, aggregate

Serial Calls (Pipeline)

Call agents one after another, feeding each step’s output into the next:
step1 = await client.call("org/parser@v1", {"file": input.file})
step2 = await client.call("org/analyzer@v1", {"parsed": step1})
step3 = await client.call("org/reporter@v1", {"analysis": step2})
return step3

Parallel Calls (Fan-out)

Call multiple agents concurrently:
import asyncio

secrets, vulns, licenses = await asyncio.gather(
    client.call("joe/leak-finder@v1", {"url": repo}),
    client.call("joe/vuln-scanner@v1", {"url": repo}),
    client.call("joe/license-checker@v1", {"url": repo}),
)

return {"secrets": secrets, "vulns": vulns, "licenses": licenses}

Map-Reduce

Split input into items, process each in parallel, aggregate:
import asyncio

# Map: process each item in parallel
mapped = await asyncio.gather(
    *[client.call("org/processor@v1", {"item": item}) for item in items]
)

# Reduce: aggregate results
reduced = await client.call("org/aggregator@v1", {"results": mapped})
return reduced

Conditional Calls

Call based on previous results:
scan = await client.call("org/quick-scan@v1", {"url": repo})

if scan["risk_level"] == "high":
    # Only do deep scan if quick scan found issues
    deep = await client.call("org/deep-scan@v1", {"url": repo})
    return {"quick": scan, "deep": deep}

return {"quick": scan}

Local Development

When you run an orchestrator locally with orch run, the CLI detects dependencies:
$ orch run joe/security-review .

Checking dependencies...

This agent has dependencies:
  joe/leak-finder@v1 (downloadable)
  joe/vuln-scanner@v1 (downloadable)
  acme/premium-scan@v1 (cloud-only)

Options:
  [1] Run on cloud (orch run) - recommended
  [2] Download 2 available deps, run locally
  [3] Cancel

Choose [1/2/3]:
Auto-download dependencies:
orch run joe/security-review --with-deps .
Local mode requires the orchagent-sdk package to be installed for dependency calls to work correctly.

Rate Limits

WhatLimit Applied
Top-level callCaller’s 1000/day limit
Sub-agent callsOrchestrator’s manifest + hop limits
Nested callsSame orchestrator’s limits
Example: User calls security-review which calls 3 agents:
  • User’s daily count: +1 (just the top-level)
  • security-review’s downstream calls: +3 (no billing)

Troubleshooting

DEPENDENCY_NOT_ALLOWED

Your agent tried to call an agent not in its manifest. Fix: Add the dependency to your manifest’s dependencies array.

MAX_HOPS_EXCEEDED

Call chain is too deep. Fix: Increase max_hops in manifest, or refactor to reduce nesting.

MAX_HOPS_TOO_LOW

Caller’s max_hops is lower than your agent’s min_required_hops. Fix: Either lower your min_required_hops or document that callers need higher limits.

DEPENDENCY_CYCLE

A→B→C→A detected. Fix: Restructure your agents to avoid circular dependencies.

STRICT_MODE_BASH_DISABLED

Your orchestrator tried to use bash but is running in strict mode. Fix: Use your custom tools (dependency agents) instead. If you need bash, set orchestration_mode: "flexible" in your manifest and republish.

STRICT_MODE_DEPENDENCY_REQUIRED

Your orchestrator tried to submit results without calling any dependency. Fix: Call at least one custom tool before submitting. In strict mode, the agent must delegate to its declared dependencies.

TIMEOUT

Request exceeded deadline. Fix: Increase timeout_ms or optimize your agent’s logic.

Strict vs Flexible Mode

Managed-loop orchestrators with dependencies run in one of two orchestration modes: strict or flexible. This controls whether the agent must delegate to its declared dependencies or can solve tasks directly with built-in tools.

Strict Mode (Default)

Orchestrators with dependencies default to strict mode. In strict mode:
  • bash is not available — removed from the tool list entirely
  • Dependency calls are required — the agent must call at least one custom tool (dependency) before submitting results
  • Built-in file tools (read_file, write_file, list_files) remain available
This ensures the declared dependency graph matches runtime behavior — if you declare dependencies, the platform enforces that they’re actually used.

Flexible Mode

Use flexible mode when your orchestrator needs the freedom to solve some tasks directly:
{
  "manifest": {
    "orchestration_mode": "flexible",
    "dependencies": [{ "id": "yourorg/scanner", "version": "v1" }]
  }
}
In flexible mode, all built-in tools (including bash) remain available. The agent can choose to use dependencies or solve tasks directly. Dependency allowlist enforcement still applies — the agent can only call agents listed in dependencies.

When to Use Each

Use strict when…Use flexible when…
Your agent exists to coordinate other agentsYour agent sometimes needs bash for lightweight tasks
You want predictable dependency-path executionDependencies are optional enhancements, not core workflow
You need the runtime DAG to match declared dependenciesYou’re prototyping and want maximum freedom

Chain Inheritance

In multi-agent chains, strict mode propagates one-way downward:
strict parent → strict child   (always — child cannot downgrade)
flexible parent → child's own mode applies
If a strict orchestrator calls a child that is configured as flexible, that child runs as strict for that execution. A child can never downgrade to flexible when called from a strict parent. This means a top-level strict orchestrator guarantees dependency-path execution through the entire chain.

Error Codes

If your agent violates strict mode rules, you’ll see these errors in the agent’s output:
ErrorCauseFix
STRICT_MODE_BASH_DISABLEDAgent attempted to use bash in strict modeUse custom tools (dependencies) instead, or set orchestration_mode: "flexible"
STRICT_MODE_DEPENDENCY_REQUIREDAgent tried to submit results without calling any dependencyCall at least one custom tool before submitting

Checking Your Mode

When you publish, the CLI echoes the effective orchestration mode:
$ orch publish
Published yourorg/security-review@v3
  orchestration_mode: strict (defaulted  set "flexible" to opt out)
To use flexible mode, set orchestration_mode: "flexible" in your manifest and republish.

Best Practices

  1. Keep agents focused - Each agent does one thing well
  2. Declare all deps - Undeclared calls are blocked
  3. Pin versions - Use exact versions (v1), not latest
  4. Handle errors - Wrap calls in try/except
  5. Set reasonable timeouts - Account for all dependency calls
  6. Use parallel calls - Fan-out when deps are independent
  7. Test locally first - Use orch run --with-deps during development

Next Steps

Build an Orchestrator

Step-by-step tutorial with complete examples

SDK Reference

Complete API reference for the orchagent-sdk package

Agent Types

Learn about different agent types and when to use them