<!--
hoody-agent Subskill (sdk)
Auto-generated by Hoody Skills Generator
Generated: 2026-06-20T00:07:48.887Z
Model: mimo-v2.5-pro + fixer:mimo-v2.5-pro
Mode: sdk


Tokens: 43424

DO NOT EDIT MANUALLY - Changes will be overwritten on next generation
-->

# hoody-agent Subskill

## 1. Overview

### Purpose

`hoody-agent` is the central AI agent orchestration service for Hoody. It manages the full lifecycle of agent sessions, tool execution, workflow orchestration, memory systems, skill management, and provider authentication. This service exposes 189 endpoints across 19 functional groups.

### When to Use This Service

Use `hoody-agent` when you need to:

- **Create and manage agent sessions** — interactive or headless conversations with LLM-backed agents
- **Execute tools** — run built-in or MCP tools either sessionless or within a live session
- **Manage workflows** — define, dispatch, monitor, and cancel multi-step agent workflows
- **Configure settings and providers** — manage LLM provider credentials, API keys, OAuth flows, model fusion composites, and global settings
- **Manage memory** — store, search, consolidate, and graph project-scoped memory records
- **Manage skills** — create, install from hub, trust, enable/disable, and edit agent skills
- **Manage todos** — file, triage, run, approve proposals, and track autonomous agent work items
- **Manage agent definitions** — create, copy, rename, configure tools/models/turns for chat agents
- **Integrate with GitHub** — authenticate, clone, commit, open PRs, sync repos
- **Monitor operations** — query logs, view statistics, track usage, manage async jobs
- **Set up recurring loops** — schedule periodic agent runs on live sessions

### Authentication Model

All endpoints (except `GET /health`) require bearer token authentication via the `Authorization: Bearer <token>` header. The token is provided when creating the SDK client. Context is further scoped by optional headers:

- `X-Hoody-Cwd` — working directory context (most resources are cwd-scoped)
- `X-Hoody-Config-Dir` — configuration directory override
- `X-Hoody-Container` — target container identifier
- `X-Hoody-Realm` — target realm (some resources reject realm headers, noted per endpoint)

Session-scoped resources require a live `session_id`. Guarded hook mutations require a two-step nonce (`begin-write` → mutation).

### Base URL

```
https://api.hoody.com
```

All SDK method paths are served under the `/api/v1/agent` prefix (set via OpenAPI `servers`). You do NOT manually add `/api/v1/agent` — the SDK handles this.

### Philosophy

Hoody-agent follows the **bounded autonomy** model: agents operate within frozen session scopes, tools run through gated choke points, destructive operations require explicit approval or nonces, and memory consolidation is human-only. Every mutation is auditable and most state transitions are CAS-guarded (compare-and-swap).

---

## 2. Core Resource Workflows

### 2.1 Settings & Configuration

Settings control process-wide behavior, ACP (Bring Your Own Agent) backends, and model fusion composites.

#### Read & Patch Global Settings

```
import { HoodyClient } from '@hoody-ai/hoody-sdk'

const client = new HoodyClient({
  baseURL: 'https://api.hoody.com',
  token: process.env.HOODY_TOKEN
})

// Read the effective merged settings (home → project → settings.local.json)
const settings = await client.agent.settings.getSettings()
console.log(settings)

// Patch home-layer settings (shallow top-level merge; nil deletes a key)
await client.agent.settings.patchSettings({
  data: {
    features: { memory: true }
  }
})

// Re-read to verify
const updated = await client.agent.settings.getSettings()
console.log(updated.features.memory) // true
```

> **Note:** `patchSettings` does a shallow top-level merge. Sending `{"features": {}}` REPLACES the entire `features` object, not individual keys within it.

#### ACP (Bring Your Own Agent) Backend Status

```
// Check which BYOA delegated-session backends are available
// (codex/claude/gemini/opencode) — enabled flag, PATH status, trust posture
const acpStatus = await client.agent.settings.getACPStatus()
console.log(acpStatus)

// Store a per-backend env value for an ACP agent
// Persisted in ~/.hoody/acp-secrets.env under acp/<agent>/<ENVKEY> (0600)
await client.agent.settings.setACPSecret('claude', 'ANTHROPIC_API_KEY', {
  data: { value: 'sk-ant-...' }
})
```

#### Model Fusion Composites

```
// List all persisted model-fusion composites
const fusions = await client.agent.settings.listFusion()
console.log(fusions)

// Include entries that failed validation (editable/deletable)
const allFusions = await client.agent.settings.listFusion({
  include_invalid: true
})

// Create or update a fusion composite by slug
await client.agent.settings.upsertFusion('my-fusion', {
  data: {
    spec: {
      name: 'my-fusion',
      method: 'router',
      members: ['anthropic/claude-opus-4-8', 'openai/gpt-4o']
    }
  }
})

// Delete a fusion composite
await client.agent.settings.deleteFusion({ slug: 'my-fusion' })
```

---

### 2.2 Agents (Chat Agent Definitions)

Agents are chat-agent definitions with frontmatter (model, tools, max-turns) and a system prompt. Agents are cwd/config_dir scoped.

#### List & Create Agents

```
// List resolved chat-agent definitions (built-in + custom)
const agents = await client.agent.agents.listAgents()
console.log(agents)

// Create a custom chat-agent definition
await client.agent.agents.createAgent({
  data: {
    name: 'code-reviewer',
    model: 'anthropic/claude-opus-4-8',
    tools: ['read', 'write', 'bash'],
    max_turns: 20,
    system_prompt: 'You are a senior code reviewer. Be thorough and precise.'
  }
})

// Verify creation
const created = await client.agent.agents.listAgents()
console.log(created.items.find(a => a.name === 'code-reviewer'))
```

#### Copy, Rename, Delete

```
// Copy an agent to a new name
await client.agent.agents.copyAgent('code-reviewer', {
  data: { new_name: 'code-reviewer-v2' }
})

// Rename a custom agent
await client.agent.agents.renameAgent('code-reviewer-v2', {
  data: { new_name: 'reviewer' }
})

// Delete a custom agent (shipped defaults and configured default are refused)
await client.agent.agents.deleteAgent({ name: 'reviewer' })
```

#### Read & Write Agent Source

```
// Read the raw markdown source (frontmatter + system prompt)
const source = await client.agent.agents.getAgentSource({ name: 'code-reviewer' })
console.log(source)

// Overwrite the raw markdown source
await client.agent.agents.putAgentSource('code-reviewer', {
  data: {
    content: '---\nmodel: anthropic/claude-opus-4-8\ntools: [read, write]\nturns: 15\n---\n\nYou are a code reviewer.'
  }
})
```

#### Configure Agent Model, Tools, Turns

```
// Set an agent's model (empty string removes the model line)
await client.agent.agents.setAgentModel('code-reviewer', {
  data: { model: 'openai/gpt-4o' }
})

// Remove the model line (uses project default)
await client.agent.agents.setAgentModel('code-reviewer', {
  data: { model: '' }
})

// Set an agent's tool allow-list (empty list = all tools)
await client.agent.agents.setAgentTools('code-reviewer', {
  data: { tools: ['read', 'write', 'bash', 'github'] }
})

// Toggle a single tool on/off
await client.agent.agents.toggleAgentTool('code-reviewer', 'bash', {
  data: {}
})

// Set max-turns
await client.agent.agents.setAgentTurns('code-reviewer', {
  data: { max_turns: 50 }
})

// Reset agent to shipped defaults (discards local customizations)
await client.agent.agents.resetAgentToShipped({ name: 'code-reviewer' })
```

---

### 2.3 Sessions

Sessions are the primary interaction unit — a conversation with a live agent bound to a realm, container, cwd, and tool mode at creation time.

#### Create, List, Get, Delete Sessions

```
// Create a new session (freezes realm/container/cwd/tool_mode at start)
const session = await client.agent.sessions.createSession({
  data: {
    agent: 'code-reviewer',
    model: 'anthropic/claude-opus-4-8'
  }
})
const sessionId = session.id

// List persisted sessions for the requesting cwd (open + closed)
const sessions = await client.agent.sessions.listSessions({
  page: 1,
  limit: 20
})
console.log(sessions)

// Get session summary (live sessions surface frozen bindings + attached flag)
const detail = await client.agent.sessions.getSession(sessionId)
console.log(detail)

// Get distinct working directories across all sessions
const cwds = await client.agent.sessions.listSessionCwds()

// Close session (teardown live connection)
await client.agent.sessions.closeSession(sessionId)

// Hard-delete (removes persisted record too)
await client.agent.sessions.deleteSession(sessionId, { hard: true })
```

#### Dispatch Turns (Messages, Prompts)

```
// Dispatch a user turn — returns {job_id}; observe agent_done on stream
const msgResult = await client.agent.sessions.postSessionMessage(sessionId, {
  data: { content: 'Review the latest commit for security issues.' }
})
console.log(msgResult.job_id)

// Dispatch a turn and block until agent_done (sync)
const syncResult = await client.agent.sessions.promptSync(sessionId, {
  data: { content: 'What files changed?' }
})
console.log(syncResult)

// Dispatch a turn and stream SSE events
const streamResult = await client.agent.sessions.promptStream(sessionId, {
  policy: 'auto_approve',
  data: { content: 'Run the test suite.' }
})
```

#### Live Session Control

```
// Switch chat agent mid-session
await client.agent.sessions.setSessionAgent(sessionId, {
  data: { agent: 'default' }
})

// Switch model mid-session
await client.agent.sessions.setSessionModel(sessionId, {
  data: { model: 'openai/gpt-4o' }
})

// Set reasoning effort (low|medium|high|xhigh or "" for default)
await client.agent.sessions.setSessionEffort(sessionId, {
  data: { effort: 'high' }
})

// Set response verbosity (normal|concise|terse|minimal)
await client.agent.sessions.setSessionVerbosity(sessionId, {
  data: { verbosity: 'concise' }
})

// Toggle HOODY_* shell-env injection for the bash tool
await client.agent.sessions.setSessionHoodyEnv(sessionId, {
  data: { enabled: true }
})

// Cancel the active turn (Esc equivalent — spares session + background tasks)
await client.agent.sessions.cancelSession(sessionId)
```

#### Gated Interactions (Questions, Confirmations)

```
// Answer a parked ask-the-user question
await client.agent.sessions.answerQuestion(sessionId, {
  data: {
    gate_id: 'q-123',
    answer: 'Use the staging environment.'
  }
})

// Run a helper model to propose answers for a parked question
await client.agent.sessions.answerAssist(sessionId, {
  data: { gate_id: 'q-123' }
})

// Approve or deny a parked tool/dir confirmation
await client.agent.sessions.confirmGate(sessionId, {
  data: {
    gate_id: 'c-456',
    approved: true
  }
})
```

#### Auto-Reply & History

```
// Arm the self-driving auto-reply loop
await client.agent.sessions.setSessionAutoReply(sessionId, {
  data: {
    enabled: true,
    rounds: 10,
    model: 'anthropic/claude-opus-4-8',
    write_opt_in: true
  }
})

// Flip write-class opt-in without re-arming (no budget reset)
await client.agent.sessions.setSessionAutoReplyWrites(sessionId, {
  data: { write_opt_in: false }
})

// Truncate session history to a turn index
await client.agent.sessions.trimSession(sessionId, {
  data: { turn_index: 5 }
})

// Replay buffered events for a live session (for resume)
const replay = await client.agent.sessions.replaySession(sessionId)
console.log(replay.min_seq, replay.max_seq)

// Attach to a session's event stream (WebSocket or SSE)
// SDK returns an event emitter / async iterable
const events = await client.agent.sessions.streamSession(sessionId, {
  since: replay.min_seq
})
```

#### Workflow Messages

```
// Inject user feedback into a running workflow on this session
await client.agent.sessions.postWorkflowMessage(sessionId, {
  data: {
    workflow_run_id: 'wr-789',
    content: 'Focus on the authentication module only.'
  }
})
```

---

### 2.4 Loops (Recurring Agent Runs)

Loops schedule periodic agent turns on a live session.

```
// List loops on a session
const loops = await client.agent.loops.listLoops(sessionId)

// Create a recurring loop
const loop = await client.agent.loops.createLoop(sessionId, {
  data: {
    interval: 300,        // seconds between runs
    max_runs: 10,
    stop_when: 'no_changes',
    cost_budget: 5.00,
    wall_budget: 3600
  }
})
const loopId = loop.id

// Update loop state: pause/resume/stop
await client.agent.loops.updateLoop(sessionId, loopId, {
  data: { paused: true }
})

// Update loop expiry
await client.agent.loops.updateLoop(sessionId, loopId, {
  data: { expires_in: 7200 }
})

// Fire the loop's next run immediately
await client.agent.loops.runLoopNow(sessionId, loopId)

// Delete the loop
await client.agent.loops.deleteLoop(sessionId, loopId)
```

---

### 2.5 Tasks (Background Subagent Tasks)

Tasks represent background work spawned by the agent during a turn.

```
// Request the session's background task snapshot (arrives on WS/SSE stream)
await client.agent.tasks.listTasks(sessionId)

// Cancel a single background task
await client.agent.tasks.cancelTask(sessionId, 'task-abc')

// Cancel ALL background tasks on the session
await client.agent.tasks.cancelAllTasks(sessionId)

// Request a task's transcript from a sequence cursor (arrives on stream)
await client.agent.tasks.requestTaskTranscript(sessionId, 'task-abc', {
  after_seq: 42
})
```

---

### 2.6 Tools

Tools are executable capabilities available to agents. They can be run sessionless (with ephemeral scope) or inside a live session.

#### Browse the Tool Catalogue

```
// List all built-in tool schemas (name, description, JSON-Schema input, read_only)
const tools = await client.agent.tools.listTools({ page: 1, limit: 50 })
console.log(tools)

// List only read-only tools (safe for planning / sessionless runs)
const readOnlyTools = await client.agent.tools.listReadOnlyTools()

// Get one tool's schema by name
const bashTool = await client.agent.tools.getTool({ name: 'bash' })
console.log(bashTool)
```

#### Run Tools (Sessionless)

```
// Run a tool through the gated choke point with NO open session
// Ephemeral session minted from X-Hoody-Cwd / X-Hoody-Config-Dir headers
const result = await client.agent.tools.runTool('bash', {
  data: {
    command: 'ls -la',
    timeout: 30
  }
})

// Run asynchronously — returns {job_id}; poll with getJob/getJobResult
const asyncResult = await client.agent.tools.runToolAsync('bash', {
  data: { command: 'npm test', timeout: 120 }
})
console.log(asyncResult.job_id)

// Run with streamed result (SSE: start → result/needs_confirmation/error → end)
const streamed = await client.agent.tools.streamTool('read', {
  data: { path: './package.json' }
})
```

#### Run Tools Inside a Live Session

```
// Run a tool through the gated choke point on a LIVE session
const sessionResult = await client.agent.tools.runSessionTool(sessionId, 'bash', {
  data: { command: 'git status' }
})

// List the effective tool set for a live session
const sessionTools = await client.agent.tools.listSessionTools(sessionId)

// List MCP (mcp__*) tools available in the session
const mcpTools = await client.agent.tools.listSessionMCPTools(sessionId)
```

---

### 2.7 Providers & Model Catalogue

Providers are LLM service integrations. Each provider has authentication methods (API key, OAuth) and a catalogue of models.

#### Browse Models & Providers

```
// List the merged model catalogue (all provider models + fusion composites)
const models = await client.agent.models.listModels({ page: 1, limit: 50 })

// Get one model by full spec
const model = await client.agent.models.getModel({ spec: 'anthropic/claude-opus-4-8' })

// Get a fusion composite by fusion/<slug> spec
const fusionModel = await client.agent.models.getModel({ spec: 'fusion/my-fusion' })

// List every catalogued LLM provider
const providers = await client.agent.models.listProviders()

// Get one provider's metadata + full model list
const provider = await client.agent.models.getProvider({ id: 'anthropic' })
```

#### Provider Authentication

```
// Report a provider's stored-credential state
const authStatus = await client.agent.models.getProviderAuth({ id: 'anthropic' })
console.log(authStatus)

// Store a provider API key (atomic ~/.hoody/.env keychainless store)
await client.agent.models.setProviderAPIKey('openai', {
  data: { api_key: 'sk-...' }
})

// Delete a provider's API key
await client.agent.models.deleteProviderAPIKey({ id: 'openai' })

// Set the default credential method (api_key | oauth)
await client.agent.models.setProviderDefault('anthropic', {
  data: { method: 'api_key' }
})
```

#### Provider OAuth Flows

```
// Start an interactive OAuth login
const oauthStart = await client.agent.models.startProviderOAuth({ id: 'anthropic' })
console.log(oauthStart.verification_uri, oauthStart.user_code)

// Poll until the user authorizes
const poll = await client.agent.models.pollProviderOAuth(
  'anthropic',
  oauthStart.job_id
)
console.log(poll.state) // "pending" | "complete"

// For manual-paste / PKCE flows, submit the authorization code
await client.agent.models.submitProviderOAuthCode('anthropic', oauthStart.job_id, {
  data: { code: 'auth-code-here' }
})

// Remove stored OAuth credentials
await client.agent.models.logoutProviderOAuth({ id: 'anthropic' })
```

#### OAuth Account Pool

```
// List the secret-free OAuth account pool for a provider
const accounts = await client.agent.models.listProviderAccounts({ id: 'anthropic' })

// Begin an OAuth login that ADDS to the account pool
const addResult = await client.agent.models.addProviderAccount('anthropic', {
  data: {}
})
// Poll with the same pollProviderOAuth flow using addResult.job_id

// Make one pooled account active
await client.agent.models.setProviderAccountActive('anthropic', 'account-key', {
  data: {}
})

// Remove a pooled account
await client.agent.models.removeProviderAccount({ id: 'anthropic', key: 'account-key' })
```

---

### 2.8 Workflows

Workflows are multi-step agent processes that run on a live session. They have definitions, runs, and dispatch mechanisms.

#### Manage Workflow Definitions

```
// List workflow definitions visible to the requesting cwd
const workflows = await client.agent.workflows.listWorkflows()

// Read one workflow definition
const def = await client.agent.workflows.getWorkflow({ name: 'code-review' })

// Create or replace a workflow definition
await client.agent.workflows.putWorkflow('code-review', {
  data: {
    definition: {
      name: 'code-review',
      summary: 'Automated code review workflow',
      steps: [
        { tool: 'read', args: { path: './src' } },
        { tool: 'bash', args: { command: 'npm test' } }
      ]
    }
  }
})

// Hide a workflow from the Workflows tab (only way to hide system workflows)
await client.agent.workflows.hideWorkflow('code-review', {
  data: { hidden: true }
})

// Delete a USER workflow (system workflows are refused)
await client.agent.workflows.deleteWorkflow({ name: 'code-review' })
```

#### Dispatch & Monitor Workflow Runs

```
// Dispatch a workflow run onto a live session
const run = await client.agent.workflows.runSessionWorkflow(sessionId, 'code-review', {
  data: { input: { branch: 'main' } }
})
const runId = run.job_id

// Snapshot in-flight + recently-finished workflow runs
const runs = await client.agent.workflows.listWorkflowRuns()

// Get a single workflow run by id
const runDetail = await client.agent.workflows.getWorkflowRun(runId)

// Cancel an in-flight workflow run
await client.agent.workflows.cancelWorkflowRun(runId)
```

---

### 2.9 Memory

Memory provides project-scoped persistent knowledge with hybrid recall (BM25 + vector + graph fusion). Memory privacy is togglable; consolidation is human-only.

#### CRUD Operations

```
// Toggle memory capture on/off
await client.agent.memory.setMemoryEnabled({ data: { enabled: true } })

// Force the memory store durability barrier
await client.agent.memory.flushMemory()

// List memory records for a project
const items = await client.agent.memory.listMemoryItems({
  project: 'my-project',
  kind: 'fact',
  limit: 20
})

// Store a new memory record
const saved = await client.agent.memory.saveMemoryItem({
  data: {
    project: 'my-project',
    kind: 'fact',
    type: 'entity',
    content: 'The auth module uses JWT tokens with 1-hour expiry.',
    metadata: { source: 'code-review', confidence: 0.95 }
  }
})

// Read one memory record
const item = await client.agent.memory.getMemoryItem(saved.id, {
  project: 'my-project'
})

// Patch a memory record
await client.agent.memory.editMemoryItem(saved.id, {
  data: { content: 'JWT tokens with 2-hour expiry as of v2.1.' }
})

// Delete a memory record (cross-project delete)
await client.agent.memory.deleteMemoryItem({
  data: { id: saved.id }
})
```

#### Search & Graph

```
// Hybrid recall across a project (BM25 + vector + graph fusion)
const searchResults = await client.agent.memory.searchMemory({
  data: {
    project: 'my-project',
    query: 'authentication token expiry',
    limit: 10
  }
})
console.log(searchResults)

// Read a project's memory relation graph (nodes, edges, stats)
const graph = await client.agent.memory.getMemoryGraph({
  project: 'my-project',
  node_type: 'entity',
  limit: 100,
  offset: 0
})

// List memory projects
const projects = await client.agent.memory.listMemoryProjects()
```

#### Consolidation

```
// Request a consolidation pass (HUMAN-ONLY: multi-LLM, irreversible)
// This requires interactive approval — autonomous callers may NOT self-approve
await client.agent.memory.consolidateMemory({
  data: { project: 'my-project' }
})
```

---

### 2.10 Skills

Skills are agent capabilities that can be created locally, installed from a hub, trusted, enabled/disabled, and edited.

#### Manage Local Skills

```
// List installed skills for the requesting cwd/config_dir
const skills = await client.agent.skills.listSkills()

// Create a new local skill
await client.agent.skills.createSkill({
  data: {
    name: 'deploy-helper',
    description: 'Assists with deployment workflows'
  }
})

// Enable/disable a skill by name
await client.agent.skills.toggleSkill({
  data: { name: 'deploy-helper', disabled: false }
})

// Set a skill's trust state (gates arbitrary code execution)
await client.agent.skills.trustSkill({
  data: { root_dir: '/path/to/skill', rel_dir: 'deploy-helper', trusted: true }
})

// Rename a skill
await client.agent.skills.renameSkill({
  data: { root_dir: '/path', rel_dir: 'old-name', new_name: 'new-name' }
})

// Delete a skill
await client.agent.skills.deleteSkill({
  data: { root_dir: '/path', rel_dir: 'deploy-helper' }
})
```

#### Read & Write Skill Source

```
// Read a skill's source body
const source = await client.agent.skills.getSkillSource({
  root_dir: '/path',
  rel_dir: 'deploy-helper'
})

// Overwrite a skill's source body
await client.agent.skills.putSkillSource({
  data: {
    root_dir: '/path',
    rel_dir: 'deploy-helper',
    content: '# Deploy Helper\n\nThis skill assists with deployments...'
  }
})
```

#### Skill Hub

```
// Search the remote skill hub
const hubResults = await client.agent.skills.searchSkillHub({ q: 'docker' })

// Preview a hub skill before install
const preview = await client.agent.skills.previewSkillHub({
  id: 'hub-skill-id-123'
})

// Install a skill from the hub (writes arbitrary skill code to disk)
await client.agent.skills.installSkillHub({
  data: { id: 'hub-skill-id-123' }
})

// Get skill-hub fetch cache statistics
const cacheStats = await client.agent.skills.getSkillHubCache()

// Clear the skill-hub fetch cache (including pins)
await client.agent.skills.clearSkillHubCache()
```

#### Import Skills

```
// Scan well-known locations for importable skills
const scanResult = await client.agent.skills.scanSkillImport()

// Import a discovered skill into the local store
await client.agent.skills.applySkillImport({
  data: { skill_id: scanResult.items[0].id }
})
```

---

### 2.11 Todos

Todos are autonomous work items that the agent can file, triage, run, and track. They support CAS-guarded updates, proposals, and orchestrator-driven execution.

#### CRUD & Lifecycle

```
// List master todos for the requesting cwd/config_dir
const todos = await client.agent.todos.listTodos({
  page: 1,
  limit: 20
})

// File a new master todo (server-side normalize, fingerprint-dedupe, defaults)
const todo = await client.agent.todos.createTodo({
  data: {
    title: 'Refactor authentication module',
    description: 'Move from session-based to JWT auth',
    tags: ['auth', 'refactor'],
    priority: 'high'
  }
})
const todoId = todo.id

// Read a todo (includes timeline + proposals)
const detail = await client.agent.todos.getTodo(todoId)

// Update a todo (CAS-guarded: pass current revision)
const revision = await client.agent.todos.getTodosRevision()
await client.agent.todos.updateTodo(todoId, {
  data: {
    revision: revision.current,
    priority: 'medium',
    tags: ['auth', 'refactor', 'security']
  }
})

// Archive a todo
await client.agent.todos.archiveTodo(todoId, { data: {} })

// Purge archived todos (permanent, irreversible, no machine-confirmation gate)
await client.agent.todos.purgeTodos({ data: {} })
```

#### Triage & Orchestrator Runs

```
// Run an LLM triage pass over the inbox
const triageResult = await client.agent.todos.triageTodos({
  data: {}
})
console.log(triageResult.job_id)

// Claim a todo for the caller
await client.agent.todos.claimTodo(todoId, { data: {} })

// Release a claimed todo
await client.agent.todos.releaseTodo(todoId, { data: {} })

// Dispatch a background worker to autonomously work a todo
const runResult = await client.agent.todos.runTodo(todoId, {
  data: {}
})
console.log(runResult.job_id, runResult.session_id)

// Cancel an in-flight orchestrator run
await client.agent.todos.cancelTodoRun(todoId, { data: {} })

// Snooze a todo until a wake time
await client.agent.todos.snoozeTodo(todoId, {
  data: { wake_at: '2025-12-01T09:00:00Z' }
})
```

#### Messages & Proposals

```
// Post a comment AND kick an orchestrator turn
await client.agent.todos.messageTodo(todoId, {
  data: { content: 'Please also update the migration scripts.' }
})

// Post a comment WITHOUT triggering an orchestrator turn
await client.agent.todos.postTodoComment(todoId, {
  data: { content: 'Linked to issue #142.' }
})

// Approve a run proposal (spawns a background worker session)
await client.agent.todos.approveTodoProposal(todoId, 'proposal-id-1', {
  data: {}
})

// Deny a run proposal
await client.agent.todos.denyTodoProposal(todoId, 'proposal-id-1', {
  data: {}
})
```

---

### 2.12 GitHub Integration

GitHub operations are cwd-scoped (git is repo/cwd-relative). Authentication supports device-flow and PAT.

#### Authentication

```
// Start a GitHub device-flow login (or add a PAT via body token)
const loginStart = await client.agent.github.githubLogin()
console.log(loginStart.user_code, loginStart.verification_uri)

// Poll until the user authorizes
const loginPoll = await client.agent.github.githubLoginPoll({
  data: {
    device_code: loginStart.device_code,
    interval: loginStart.interval,
    expires_in: loginStart.expires_in
  }
})
console.log(loginPoll.status)

// Check GitHub authentication state
const authStatus = await client.agent.github.githubAuthStatus()
console.log(authStatus)
```

#### Repository Operations

```
// List known/configured repos
const repos = await client.agent.github.githubRepos()

// Clone a GitHub repository (hardened argv builder, cwd-scoped)
await client.agent.github.githubClone({
  data: { repo: 'owner/repo-name' }
})

// List branches for the requesting cwd's repo
const branches = await client.agent.github.githubBranches()

// Get the git working-tree status for the requesting cwd
const status = await client.agent.github.githubStatus()
console.log(status)
```

#### Write Operations

```
// Stage all changes and commit (cwd-scoped, destructive)
await client.agent.github.githubCommit({
  data: {
    message: 'feat: add authentication module',
    files: ['src/auth.ts', 'tests/auth.test.ts']
  }
})

// Open a pull request (cwd-scoped, destructive)
await client.agent.github.githubPullRequest({
  data: {
    title: 'feat: JWT authentication',
    body: 'Replaces session-based auth with JWT tokens.',
    base: 'main',
    head: 'feature/jwt-auth'
  }
})

// Sync (fetch → pull → push as one logical unit)
// Stops at FIRST non-ok step; ?direction for pull-only or push-only
await client.agent.github.githubSync({
  data: { direction: 'pull' }
})
```

---

### 2.13 Hooks

Hooks are lifecycle callbacks that fire on session events. All hook mutations require a two-step nonce (`begin-write` → mutation) and a live `session_id`.

#### Read Hooks

```
// List lifecycle hooks for a live session
const hooks = await client.agent.hooks.listHooks({
  session_id: sessionId
})
console.log(hooks)

// Reload hook configuration from disk
await client.agent.hooks.reloadHooks({
  data: { session_id: sessionId }
})
```

#### Guarded Write Flow

```
// Step 1: Obtain the two-step write nonce
const nonce = await client.agent.hooks.beginHookWrite({
  data: {
    session_id: sessionId,
    op: 'upsert' // 'upsert' | 'delete' | 'toggle' | 'set_disabled'
  }
})

// Step 2: Create or update a hook using the nonce
await client.agent.hooks.upsertHook({
  data: {
    session_id: sessionId,
    nonce: nonce.nonce,
    event: 'on_file_write',
    command: 'npm run lint -- --fix',
    enabled: true
  }
})

// Toggle a single hook (also requires begin-write nonce)
const toggleNonce = await client.agent.hooks.beginHookWrite({
  data: { session_id: sessionId, op: 'toggle' }
})
await client.agent.hooks.toggleHook({
  data: {
    session_id: sessionId,
    nonce: toggleNonce.nonce,
    hook_id: 'hook-123'
  }
})

// Disable all hooks at once (requires begin-write nonce)
const disableNonce = await client.agent.hooks.beginHookWrite({
  data: { session_id: sessionId, op: 'set_disabled' }
})
await client.agent.hooks.disableAllHooks({
  data: {
    session_id: sessionId,
    nonce: disableNonce.nonce,
    disabled: true
  }
})

// Delete a hook (requires begin-write nonce)
const deleteNonce = await client.agent.hooks.beginHookWrite({
  data: { session_id: sessionId, op: 'delete' }
})
await client.agent.hooks.deleteHook({
  data: {
    session_id: sessionId,
    nonce: deleteNonce.nonce,
    hook_id: 'hook-123'
  }
})
```

#### Test & Trust

```
// Test-fire a hook (EXECUTES the arbitrary command NOW)
await client.agent.hooks.testHook({
  data: {
    session_id: sessionId,
    hook_id: 'hook-123'
  }
})

// Acknowledge the hook-trust prompt
await client.agent.hooks.ackHookTrust({
  data: { session_id: sessionId }
})
```

---

### 2.14 Headless Runs

Headless runs drive the full agent loop over an ephemeral session for one-shot tasks.

```
// Drive the full agent loop once — returns 202 {job_id} for async
const headlessRun = await client.agent.headless.createHeadlessRun({
  data: {
    prompt: 'Analyze the repository structure and summarize the architecture.',
    model: 'anthropic/claude-opus-4-8',
    tools: ['read', 'bash'],
    output_format: 'json'
  }
})
console.log(headlessRun.job_id)

// Poll for result
const jobResult = await client.agent.jobs.getJob(headlessRun.job_id)
console.log(jobResult.status)

// Get the final result when complete
const result = await client.agent.jobs.getJobResult(headlessRun.job_id)
console.log(result)
```

---

### 2.15 Jobs (Async Operations)

Jobs track async operations dispatched by various endpoints (headless runs, tool runs, workflow runs, todo triage, etc.).

```
// Get the status of an async job
const job = await client.agent.jobs.getJob({ id: 'job-id-123' })
console.log(job.status) // pending | running | succeeded | failed | canceled

// Get the result of a completed job
const result = await client.agent.jobs.getJobResult({ id: 'job-id-123' })
console.log(result)

// Delete/cancel a job — cancels pending/running jobs (transitions to 'canceled'),
// or deletes a terminal job's historical record (idempotent)
await client.agent.jobs.deleteJob({ id: 'job-id-123' })
```

---

### 2.16 Logs

Logs provide access to the active-supervisor log stream with filtering, statistics, and real-time tailing.

```
// Query logs with filters
const logs = await client.agent.logs.queryLogs({
  source: 'agent',
  level: 'error',
  limit: 100
})
console.log(logs)

// Read one log entry by ref (always redacted)
const entry = await client.agent.logs.readLogEntry({ ref: 'ref-abc-123' })

// List available log sources/facets
const sources = await client.agent.logs.logsSources()

// Get log volume/level statistics
const stats = await client.agent.logs.logsStats()
console.log(stats)

// Tail the log stream via SSE
// (cursor advances each round; drains the ring for resume)
const logStream = await client.agent.logs.streamLogs({
  source: 'agent',
  level: 'info',
  limit: 50
})
```

---

### 2.17 Discovery

Discovery endpoints list available realms and containers for binding sessions.

```
// List realms visible to the token
const realms = await client.agent.discovery.listRealms()
console.log(realms)

// List containers in a realm (for session binding)
const containers = await client.agent.discovery.listContainers({
  X_Hoody_Realm: 'realm-id-123'
})
console.log(containers)
```

---

### 2.18 System

System endpoints provide API documentation, health checks, metrics, and OpenAPI specs.

```
// Health check (unauthenticated, always returns 200)
const health = await client.agent.system.healthCheck()
console.log(health)

// Prometheus metrics (hoody_agent_* series)
const metrics = await client.agent.system.metrics()

// OpenAPI spec (JSON)
const specJSON = await client.agent.system.openapiJSON()

// OpenAPI spec (YAML)
const specYAML = await client.agent.system.openapiYAML()

// API documentation UI (auth-gated)
const docs = await client.agent.system.docs()
```

---

### 2.19 Statistics & Usage

```
// Cross-session usage aggregate
const stats = await client.agent.statistics.getStatistics({ scope: 'cwd' })

// Per-(provider, account) usage rollup
const byAccount = await client.agent.statistics.usageByAccount({
  since: Math.floor(Date.now() / 1000) - 86400 // last 24 hours
})

// Per-(model, provider) usage rollup
const byModel = await client.agent.statistics.usageByModel()
console.log(byModel)
```

---

## 3. Advanced Operations

### 3.1 Full Session Lifecycle with Workflow

```
import { HoodyClient } from '@hoody-ai/hoody-sdk'

const client = new HoodyClient({
  baseURL: 'https://api.hoody.com',
  token: process.env.HOODY_TOKEN
})

async function fullSessionWorkflow() {
  // 1. Discover available realms and containers
  const realms = await client.agent.discovery.listRealms()
  const targetRealm = realms.items[0]
  const containers = await client.agent.discovery.listContainers({
    X_Hoody_Realm: targetRealm.id
  })

  // 2. Create a session bound to the target
  const session = await client.agent.sessions.createSession({
    X_Hoody_Realm: targetRealm.id,
    data: {
      agent: 'code-reviewer',
      model: 'anthropic/claude-opus-4-8'
    }
  })
  const sid = session.id

  // 3. Attach to the event stream for real-time updates
  const events = await client.agent.sessions.streamSession(sid)

  // 4. Dispatch an initial turn
  await client.agent.sessions.postSessionMessage(sid, {
    data: { content: 'Start the code review workflow.' }
  })

  // 5. Dispatch a workflow on this session
  const wfRun = await client.agent.workflows.runSessionWorkflow(
    sid, 'code-review', {
    data: { input: { branch: 'feature/auth' } }
  })

  // 6. Monitor the workflow run
  let runStatus
  do {
    await new Promise(r => setTimeout(r, 2000))
    runStatus = await client.agent.workflows.getWorkflowRun(wfRun.job_id)
  } while (runStatus.status === 'running')

  // 7. Inject follow-up feedback
  await client.agent.sessions.postWorkflowMessage(sid, {
    data: {
      workflow_run_id: wfRun.job_id,
      content: 'Also check for SQL injection vulnerabilities.'
    }
  })

  // 8. Handle a parked confirmation gate if one appears
  // (In practice, you'd listen on the event stream for gate events)
  // await client.agent.sessions.confirmGate(sid, {
  //   data: { gate_id: 'c-xyz', approved: true }
  // })

  // 9. Trim history if the session grows too large
  await client.agent.sessions.trimSession(sid, {
    data: { turn_index: 10 }
  })

  // 10. Gracefully close the session
  await client.agent.sessions.closeSession(sid)
}
```

### 3.2 Provider Onboarding Flow

```
async function onboardProvider(client: HoodyClient) {
  // 1. List available providers
  const providers = await client.agent.models.listProviders()
  const anthropic = providers.items.find(p => p.id === 'anthropic')

  // 2. Check current auth state
  const authStatus = await client.agent.models.getProviderAuth({ id: 'anthropic' })
  console.log('Current auth:', authStatus)

  // 3a. Option A: Set API key
  await client.agent.models.setProviderAPIKey('anthropic', {
    data: { api_key: 'sk-ant-api03-...' }
  })

  // 3b. Option B: OAuth flow
  const oauthStart = await client.agent.models.startProviderOAuth({ id: 'anthropic' })
  console.log(`Visit: ${oauthStart.verification_uri}`)
  console.log(`Code: ${oauthStart.user_code}`)

  // Poll until authorized
  let poll
  do {
    await new Promise(r => setTimeout(r, oauthStart.interval * 1000))
    poll = await client.agent.models.pollProviderOAuth(
      'anthropic', oauthStart.job_id
    )
  } while (poll.state === 'pending')

  // 4. Set default method
  await client.agent.models.setProviderDefault('anthropic', {
    data: { method: 'oauth' }
  })

  // 5. Verify models are available
  const models = await client.agent.models.listModels()
  const anthropicModels = models.items.filter(
    m => m.spec.startsWith('anthropic/')
  )
  console.log('Available models:', anthropicModels.map(m => m.spec))

  // 6. Create a fusion composite from multiple providers
  await client.agent.settings.upsertFusion('smart-routing', {
    data: {
      spec: {
        name: 'smart-routing',
        method: 'router',
        members: ['anthropic/claude-opus-4-8', 'openai/gpt-4o']
      }
    }
  })

  // 7. Verify the fusion appears in the model catalogue
  const fusionModel = await client.agent.models.getModel({ spec: 'fusion/smart-routing' })
  console.log('Fusion available:', fusionModel)
}
```

### 3.3 Autonomous Todo Pipeline

```
async function todoPipeline(client: HoodyClient) {
  // 1. File multiple todos
  const todo1 = await client.agent.todos.createTodo({
    data: {
      title: 'Upgrade dependencies',
      tags: ['maintenance'],
      priority: 'medium'
    }
  })
  const todo2 = await client.agent.todos.createTodo({
    data: {
      title: 'Add rate limiting to API',
      tags: ['security'],
      priority: 'high'
    }
  })

  // 2. Run LLM triage pass over the inbox
  const triage = await client.agent.todos.triageTodos({ data: {} })

  // 3. Poll the triage job
  let triageJob
  do {
    await new Promise(r => setTimeout(r, 2000))
    triageJob = await client.agent.jobs.getJob(triage.job_id)
  } while (triageJob.status === 'running')

  // 4. Claim a high-priority todo
  await client.agent.todos.claimTodo(todo2.id, { data: {} })

  // 5. Dispatch a background worker to work it autonomously
  const run = await client.agent.todos.runTodo(todo2.id, { data: {} })
  console.log('Worker dispatched:', run.job_id, run.session_id)

  // 6. Add context via messages
  await client.agent.todos.messageTodo(todo2.id, {
    data: { content: 'Use the express-rate-limit package.' }
  })

  // 7. Monitor the run
  let job
  do {
    await new Promise(r => setTimeout(r, 5000))
    job = await client.agent.jobs.getJob(run.job_id)
  } while (job.status === 'running')

  // 8. Review any proposals that emerged
  const updated = await client.agent.todos.getTodo(todo2.id)
  if (updated.proposals?.length > 0) {
    await client.agent.todos.approveTodoProposal(
      todo2.id, updated.proposals[0].id, { data: {} }
    )
  }

  // 9. Archive completed todos
  await client.agent.todos.archiveTodo(todo1.id, { data: {} })
  await client.agent.todos.archiveTodo(todo2.id, { data: {} })
}
```

### 3.4 Skill Installation & Trust Workflow

```
async function skillWorkflow(client: HoodyClient) {
  // 1. Search the hub for relevant skills
  const search = await client.agent.skills.searchSkillHub({ q: 'docker' })

  // 2. Preview before installing
  const preview = await client.agent.skills.previewSkillHub({
    id: search.items[0].id
  })
  console.log('Preview:', preview.summary)

  // 3. Install the skill
  await client.agent.skills.installSkillHub({
    data: { id: search.items[0].id }
  })

  // 4. Verify installation
  const skills = await client.agent.skills.listSkills()
  const installed = skills.items.find(s => s.name === search.items[0].name)

  // 5. Trust the skill (required for code execution)
  await client.agent.skills.trustSkill({
    data: { root_dir: installed.root_dir, rel_dir: installed.rel_dir, trusted: true }
  })

  // 6. Enable the skill
  await client.agent.skills.toggleSkill({
    data: { name: installed.name, disabled: false }
  })

  // 7. Edit the source if needed
  const source = await client.agent.skills.getSkillSource({
    root_dir: installed.root_dir,
    rel_dir: installed.rel_dir
  })

  await client.agent.skills.putSkillSource({
    data: {
      root_dir: installed.root_dir,
      rel_dir: installed.rel_dir,
      content: source.body + '\n\n## Custom Notes\nAdded by team.'
    }
  })

  // 8. Clear the hub cache if storage is a concern
  await client.agent.skills.clearSkillHubCache()
}
```

### 3.5 Error Recovery Patterns

```
async function resilientJobPolling(client: HoodyClient, jobId: string) {
  const MAX_RETRIES = 30
  const POLL_INTERVAL_MS = 3000

  for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
    try {
      const job = await client.agent.jobs.getJob(jobId)

      switch (job.status) {
        case 'succeeded':
          return await client.agent.jobs.getJobResult(jobId)
        case 'failed':
          console.error('Job failed:', job.error)
          throw new Error(`Job ${jobId} failed: ${job.error}`)
        case 'canceled':
          throw new Error(`Job ${jobId} was canceled`)
        case 'pending':
        case 'running':
          await new Promise(r => setTimeout(r, POLL_INTERVAL_MS))
          break
      }
    } catch (err) {
      if (err.message.includes('failed') || err.message.includes('canceled')) {
        throw err // Terminal — rethrow
      }
      console.warn(`Poll attempt ${attempt + 1} failed, retrying...`)
      await new Promise(r => setTimeout(r, POLL_INTERVAL_MS))
    }
  }

  // If we exhaust retries, cancel the stuck job
  await client.agent.jobs.deleteJob(jobId)
  throw new Error(`Job ${jobId} timed out after ${MAX_RETRIES} polls`)
}
```

---

## 4. Quick Reference

### Endpoint Groups Summary

| Group | SDK Namespace | Endpoint Count | Scope |
|---|---|---|---|
| **Settings** | `client.agent.settings` | 8 | global/no-realm |
| **Agents** | `client.agent.agents` | 13 | cwd/config_dir-scoped |
| **Discovery** | `client.agent.discovery` | 6 | global-read |
| **System** | `client.agent.system` | 5 | global/no-realm |
| **GitHub** | `client.agent.github` | 13 | cwd-scoped |
| **Headless** | `client.agent.headless` | 1 | cwd-scoped |
| **Hooks** | `client.agent.hooks` | 10 | session-scoped |
| **Jobs** | `client.agent.jobs` | 3 | global |
| **Logs** | `client.agent.logs` | 5 | active-only |
| **Memory** | `client.agent.memory` | 13 | project-scoped |
| **Models** | `client.agent.models` | 18 | global/no-realm |
| **Sessions** | `client.agent.sessions` | 24 | cwd-scoped |
| **Loops** | `client.agent.loops` | 7 | session-scoped |
| **Tasks** | `client.agent.tasks` | 4 | session-scoped |
| **Tools** | `client.agent.tools` | 14 | catalogue-wide / session-scoped |
| **Workflows** | `client.agent.workflows` | 10 | cwd-scoped / session-scoped |
| **Skills** | `client.agent.skills` | 16 | cwd/config_dir-scoped |
| **Statistics** | `client.agent.statistics` | 3 | cwd-scoped / global |
| **Todos** | `client.agent.todos` | 16 | cwd/config_dir-scoped |

### Common Headers

| Header | Purpose | Required |
|---|---|---|
| `X-Hoody-Cwd` | Working directory context | Most endpoints |
| `X-Hoody-Config-Dir` | Configuration directory override | Optional |
| `X-Hoody-Container` | Target container | Session creation |
| `X-Hoody-Realm` | Target realm | Some endpoints reject this |

### Response Patterns

- **List endpoints** return `{ items: [], meta: { total, page, limit } }` with pagination via `x-pagination` header
- **Async operations** return `{ job_id: string }` — poll with `getJob` / `getJobResult`
- **Session events** arrive on WebSocket/SSE streams, not inline
- **CAS-guarded updates** require passing `revision` from a prior read
- **Hook mutations** require a `nonce` from `beginHookWrite`
- **Health** is the only unauthenticated endpoint (always HTTP 200)