# Agent: Session leases **Page:** api/agent/sessions/leases [Download Raw Markdown](./api/agent/sessions/leases.md) --- # Agent: Session leases A session exposes two independent, renewable lease resources, both in-memory and both restart-resilient only insofar as the gateway still recognises them. The **approver lease** grants the exclusive right to answer a session's approval gates. Every decision on an "always" session whose lease was minted (`/confirm`, a WS confirm frame, or a gated tool-run confirmation) must present the current capability. The capability is returned once, on acquire. A capability minted for another session, a fenced generation, or no capability at all is rejected; an expired capability also fails closed, the requirement persists until someone re-acquires. The **attachment lease** keeps a live session, and especially a parked approval gate, alive against the 5-minute idle reaper, so a client that dispatched a turn and disconnected can reconnect and still answer. It is a separate resource with its own expiry, and renewing it does not renew the approver lease. When a lease expires without renewal the consequence is fail-closed in both cases, but the meaning differs: - An expired **approver lease** keeps the requirement active. No holder has the right to answer; the gate stays parked until some caller acquires a new capability. Expiry never lets anyone answer. - An expired **attachment lease** no longer suppresses the idle reaper. The session, and any gate parked on it, may be torn down at the next reaper sweep. Re-acquire to resume protection. Leases are in-memory on the daemon. After a daemon restart `GET /sessions/{id}/approval` reports `held:false`, and a client must acquire again. Holder strings are never compared; only capabilities authorise. The capability returned by acquire is shown once. Store it. The gateway will not show it again. All endpoints below live on the agent gateway of the bound container. The base URL takes the form `https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com`. ## Approver lease The approver lease is the only thing that lets you answer a parked gate on an "always" session. Acquire, renew, and release act on the session's single approver lease. ### `POST /api/v1/agent/sessions/{id}/approver-lease` Acquire the right to answer this session's gates. The response carries the capability exactly once. The daemon verifies the capability at every decision consumption: `/confirm`, a WS confirm frame, and the confirmed re-issue of a gated tool run. While a gate is parked and a lease change happens, the daemon broadcasts `event.decision_requirements { gate_id, generation, lease_required }` so peers can re-arm. Takeover needs proof. While a lease is live, a second `acquire` is `409 approver_lease_held` unless the request presents the current capability (`X-Hoody-Approver-Lease` header or `body.lease`). `replace:true` alone never authorises, and `holder` strings are never compared. #### Parameters | Name | In | Type | Required | Description | |------|-----|------|----------|-------------| | `id` | path | string | Yes | Session identifier. | | `X-Hoody-Cwd` | header | string | No | Per-request working-directory scope: the `.hoody` project layer / record cwd / tool+workflow cwd. | | `X-Hoody-Config-Dir` | header | string | No | Per-request `--config-dir` override selecting which on-disk `.hoody` install a stateless read/write resolves against. | | `X-Hoody-Container` | header | string | No | Per-request bound remote container (omitted = local). Rejected (400) on routes with no container dimension. | | `X-Hoody-Realm` | header | string | No | Per-request realm selector: `"global"` or a 24-hex id (also accepted as `?realm=`). Rejected (400 `realm_scope_unsupported`) on active-only / no-realm routes. | | `realm` | query | string | No | Per-request realm selector (the in-query alias of `X-Hoody-Realm`, read only when the header is absent). | #### Request body | Field | Type | Required | Description | |-------|------|----------|-------------| | `holder` | string | Yes | Opaque per-caller id (1 to 64 printable ASCII, no spaces) that identifies this client as the holder (reported as `holder` on the `event.gate_resolved` stream event). Never a credential; an empty holder is `400`. | | `ttl_ms` | integer | No | Requested lifetime in milliseconds (the daemon clamps to its bounds). | | `replace` | boolean | No | Documentation only. A live lease is taken over ONLY by presenting its current capability as proof; `replace:true` without the proof is still `409 approver_lease_held`. After expiry or release an acquire needs no proof. | | `lease` | string | No | The capability, used as proof an acquire presents to take over a live lease (alternative to the `X-Hoody-Approver-Lease` header). | #### Request example ```bash curl -X POST "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/sessions/{id}/approver-lease" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"holder": "ops-console-7a3"}' ``` ```ts import { HoodyClient } from 'hoody-sdk'; const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN }); await client.agent.sessions.claimApproverLease('{id}', { holder: 'ops-console-7a3' }); ``` #### Responses ```json { "lease": "alc_01HF3ZP7MX8HQ6VYJK5R2N9C4D", "held": true, "holder": "ops-console-7a3", "generation": 1, "expires_at": "2025-03-19T14:22:08.314Z", "epoch": "ep_01HF3ZP5T7K9M2X8V4N6YQB1RA" } ``` | Field | Type | Description | |-------|------|-------------| | `lease` | string | The capability to present as `X-Hoody-Approver-Lease` on `/confirm` (acquire only). | | `held` | boolean | Whether a lease is currently held. | | `holder` | string | The holder identity (this gateway connection, plus the optional body holder). | | `generation` | integer | Lease generation; a decision echoes it, and a replacement bumps it (fencing the previous holder). | | `expires_at` | string | RFC3339 expiry; renew before it. | | `epoch` | string | The daemon execution epoch the lease is bound to; a restart starts a new one and every lease must be re-acquired. | ```json { "code": "bad_request", "message": "invalid request" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `bad_request` | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. | | `realm_scope_unsupported` | Realm scope unsupported | A per-request realm header was supplied to an active-only / global-no-realm RPC, which has no realm dimension to scope. | Omit the realm header on this route, or open a session to scope by realm. | | `invalid_realm` | Invalid realm selector | The realm selector is malformed (not ""/"global"/a 24-hex id). | Pass a valid realm selector. | ```json { "code": "forbidden", "message": "request must arrive through the Hoody proxy" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `forbidden` | Forbidden (Source IP Guard) | The request did not come through the program's URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with `hoody agent …`. | ```json { "code": "not_found", "message": "resource not found" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `not_found` | Not found | The requested resource does not exist. | Verify the path and identifier. | ```json { "code": "approver_lease_held", "message": "another holder owns the approver lease" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `approver_lease_held` | Approver lease held | Another unexpired holder owns this session's approver lease. | Wait for it to expire or be released, or take it over by presenting its current capability as proof (`X-Hoody-Approver-Lease` / `body.lease`). `replace:true` alone never authorises. | ```json { "code": "payload_too_large", "message": "request body exceeds the configured size limit" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `payload_too_large` | Payload too large | The request body exceeds the configured size cap (`MaxBodyBytes`). The gateway rejects an oversized body at the edge (`http.MaxBytesReader`) before the handler reads it. A well-formed-but-large body is a size violation, not a JSON syntax error. | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. | ```json { "code": "rate_limited", "message": "request rate limit exceeded" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `rate_limited` | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the `Retry-After` header and retry; reduce the request rate. | ```json { "code": "internal_error", "message": "internal server error" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `internal_error` | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. | ```json { "code": "service_unavailable", "message": "service unavailable" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `service_unavailable` | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor `Retry-After` and retry. | ### `PATCH /api/v1/agent/sessions/{id}/approver-lease` Renew the approver lease. Extends the presented lease (`X-Hoody-Approver-Lease` header or `body.lease`). A capability that does not verify returns `409 approver_lease_invalid`; a lease that has already lapsed returns `410 approver_lease_expired` (re-acquire). #### Parameters | Name | In | Type | Required | Description | |------|-----|------|----------|-------------| | `id` | path | string | Yes | Session identifier. | | `X-Hoody-Approver-Lease` | header | string | No | The approver-lease capability returned by `POST /sessions/{id}/approver-lease`. On renew/release it names the lease to act on. | | `X-Hoody-Cwd` | header | string | No | Per-request working-directory scope. | | `X-Hoody-Config-Dir` | header | string | No | Per-request `--config-dir` override. | | `X-Hoody-Container` | header | string | No | Per-request bound remote container. | | `X-Hoody-Realm` | header | string | No | Per-request realm selector. | | `realm` | query | string | No | Per-request realm selector (in-query alias). | #### Request body | Field | Type | Required | Description | |-------|------|----------|-------------| | `ttl_ms` | integer | No | Requested lifetime in milliseconds (the daemon clamps to its bounds). | | `lease` | string | No | The capability (alternative to the `X-Hoody-Approver-Lease` header). | #### Request example ```bash curl -X PATCH "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/sessions/{id}/approver-lease" \ -H "Authorization: Bearer " \ -H "X-Hoody-Approver-Lease: alc_01HF3ZP7MX8HQ6VYJK5R2N9C4D" \ -H "Content-Type: application/json" \ -d '{"ttl_ms": 900000}' ``` ```ts import { HoodyClient } from 'hoody-sdk'; const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN }); await client.agent.sessions.renewApproverLease('{id}', { ttl_ms: 900000 }, { XHoodyApproverLease: 'alc_01HF3ZP7MX8HQ6VYJK5R2N9C4D' }); ``` #### Responses ```json { "held": true, "holder": "ops-console-7a3", "generation": 1, "expires_at": "2025-03-19T14:37:08.314Z", "epoch": "ep_01HF3ZP5T7K9M2X8V4N6YQB1RA" } ``` | Field | Type | Description | |-------|------|-------------| | `lease` | string | The capability. Present ONLY on acquire and never shown again; store it. | | `held` | boolean | Whether a lease is currently held. | | `holder` | string | The holder identity. | | `generation` | integer | Lease generation; a decision echoes it, and a replacement bumps it (fencing the previous holder). | | `expires_at` | string | RFC3339 expiry; renew before it. | | `epoch` | string | The daemon execution epoch. | ```json { "code": "bad_request", "message": "invalid request" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `bad_request` | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. | | `realm_scope_unsupported` | Realm scope unsupported | A per-request realm header was supplied to an active-only / global-no-realm RPC, which has no realm dimension to scope. | Omit the realm header on this route, or open a session to scope by realm. | | `invalid_realm` | Invalid realm selector | The realm selector is malformed (not ""/"global"/a 24-hex id). | Pass a valid realm selector. | ```json { "code": "forbidden", "message": "request must arrive through the Hoody proxy" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `forbidden` | Forbidden (Source IP Guard) | The request did not come through the program's URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with `hoody agent …`. | ```json { "code": "not_found", "message": "resource not found" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `not_found` | Not found | The requested resource does not exist. | Verify the path and identifier. | ```json { "code": "approver_lease_invalid", "message": "the approver lease does not verify" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `approver_lease_invalid` | Approver lease invalid | The presented capability does not verify for this session (wrong session, a fenced generation, or a daemon restart). | Re-acquire the lease (`POST /sessions/{id}/approver-lease`). | ```json { "code": "approver_lease_expired", "message": "the approver lease expired" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `approver_lease_expired` | Approver lease expired | The presented lease lapsed; an expired lease never authorises a decision. | Re-acquire the lease (`POST /sessions/{id}/approver-lease`). | ```json { "code": "payload_too_large", "message": "request body exceeds the configured size limit" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `payload_too_large` | Payload too large | The request body exceeds the configured size cap (`MaxBodyBytes`). | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. | ```json { "code": "rate_limited", "message": "request rate limit exceeded" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `rate_limited` | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the `Retry-After` header and retry; reduce the request rate. | ```json { "code": "internal_error", "message": "internal server error" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `internal_error` | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. | ```json { "code": "service_unavailable", "message": "service unavailable" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `service_unavailable` | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor `Retry-After` and retry. | ### `DELETE /api/v1/agent/sessions/{id}/approver-lease` Release the approver lease. Releases the presented lease (`X-Hoody-Approver-Lease`). A capability that does not verify returns `409 approver_lease_invalid`; a lease that has already lapsed returns `410 approver_lease_expired`. #### Parameters | Name | In | Type | Required | Description | |------|-----|------|----------|-------------| | `id` | path | string | Yes | Session identifier. | | `X-Hoody-Approver-Lease` | header | string | No | The approver-lease capability. On release it names the lease to act on. | | `X-Hoody-Cwd` | header | string | No | Per-request working-directory scope. | | `X-Hoody-Config-Dir` | header | string | No | Per-request `--config-dir` override. | | `X-Hoody-Container` | header | string | No | Per-request bound remote container. | | `X-Hoody-Realm` | header | string | No | Per-request realm selector. | | `realm` | query | string | No | Per-request realm selector (in-query alias). | This endpoint accepts no request body. #### Request example ```bash curl -X DELETE "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/sessions/{id}/approver-lease" \ -H "Authorization: Bearer " \ -H "X-Hoody-Approver-Lease: alc_01HF3ZP7MX8HQ6VYJK5R2N9C4D" ``` ```ts import { HoodyClient } from 'hoody-sdk'; const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN }); await client.agent.sessions.releaseApproverLease('{id}', { XHoodyApproverLease: 'alc_01HF3ZP7MX8HQ6VYJK5R2N9C4D' }); ``` #### Responses ```json { "held": false, "holder": "ops-console-7a3", "generation": 1, "expires_at": "2025-03-19T14:22:08.314Z", "epoch": "ep_01HF3ZP5T7K9M2X8V4N6YQB1RA" } ``` | Field | Type | Description | |-------|------|-------------| | `lease` | string | The capability. Present ONLY on acquire and never shown again; store it. | | `held` | boolean | Whether a lease is currently held. | | `holder` | string | The holder identity. | | `generation` | integer | Lease generation. | | `expires_at` | string | RFC3339 expiry. | | `epoch` | string | The daemon execution epoch. | ```json { "code": "bad_request", "message": "invalid request" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `bad_request` | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. | | `realm_scope_unsupported` | Realm scope unsupported | A per-request realm header was supplied to an active-only / global-no-realm RPC, which has no realm dimension to scope. | Omit the realm header on this route, or open a session to scope by realm. | | `invalid_realm` | Invalid realm selector | The realm selector is malformed (not ""/"global"/a 24-hex id). | Pass a valid realm selector. | ```json { "code": "forbidden", "message": "request must arrive through the Hoody proxy" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `forbidden` | Forbidden (Source IP Guard) | The request did not come through the program's URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with `hoody agent …`. | ```json { "code": "not_found", "message": "resource not found" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `not_found` | Not found | The requested resource does not exist. | Verify the path and identifier. | ```json { "code": "approver_lease_invalid", "message": "the approver lease does not verify" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `approver_lease_invalid` | Approver lease invalid | The presented capability does not verify for this session (wrong session, a fenced generation, or a daemon restart). | Re-acquire the lease (`POST /sessions/{id}/approver-lease`). | ```json { "code": "approver_lease_expired", "message": "the approver lease expired" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `approver_lease_expired` | Approver lease expired | The presented lease lapsed; an expired lease never authorises a decision. | Re-acquire the lease (`POST /sessions/{id}/approver-lease`). | ```json { "code": "rate_limited", "message": "request rate limit exceeded" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `rate_limited` | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the `Retry-After` header and retry; reduce the request rate. | ```json { "code": "internal_error", "message": "internal server error" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `internal_error` | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. | ```json { "code": "service_unavailable", "message": "service unavailable" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `service_unavailable` | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor `Retry-After` and retry. | ## Attachment lease The attachment lease keeps a live session, and especially a parked gate, alive across client disconnects. The acquire is decided atomically against the reaper: a session whose close is already committed answers `404`. The attachment lease only keeps the session alive; the approver lease is a separate resource with its own expiry, so an expired approver lease still fails closed even while an attachment lease is live. ### `POST /api/v1/agent/sessions/{id}/attachments` Hold a live session (and its parked gate) alive. Returns `{ lease_id, expires_at }`. Renew with `PATCH` before expiry, release with `DELETE`. Default lifetime is 15 minutes, capped at 60 minutes. #### Parameters | Name | In | Type | Required | Description | |------|-----|------|----------|-------------| | `id` | path | string | Yes | Session identifier. | | `X-Hoody-Cwd` | header | string | No | Per-request working-directory scope. | | `X-Hoody-Config-Dir` | header | string | No | Per-request `--config-dir` override. | | `X-Hoody-Container` | header | string | No | Per-request bound remote container. | | `X-Hoody-Realm` | header | string | No | Per-request realm selector. | | `realm` | query | string | No | Per-request realm selector (in-query alias). | #### Request body | Field | Type | Required | Description | |-------|------|----------|-------------| | `ttl_ms` | integer | No | Requested lifetime in milliseconds (default 15m, capped at 60m). | #### Request example ```bash curl -X POST "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/sessions/{id}/attachments" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"ttl_ms": 900000}' ``` ```ts import { HoodyClient } from 'hoody-sdk'; const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN }); await client.agent.sessions.claimAttachment('{id}', { ttl_ms: 900000 }); ``` #### Responses ```json { "lease_id": "atl_01HF3ZQ4MX9JQ7WZK6S3OAE5E2", "expires_at": "2025-03-19T14:37:08.314Z" } ``` | Field | Type | Description | |-------|------|-------------| | `lease_id` | string | The lease id (send it on `PATCH`/`DELETE` to renew/release). | | `expires_at` | string | RFC3339 expiry; renew before it or the session may be reaped once idle. | ```json { "code": "bad_request", "message": "invalid request" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `bad_request` | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. | | `realm_scope_unsupported` | Realm scope unsupported | A per-request realm header was supplied to an active-only / global-no-realm RPC, which has no realm dimension to scope. | Omit the realm header on this route, or open a session to scope by realm. | | `invalid_realm` | Invalid realm selector | The realm selector is malformed (not ""/"global"/a 24-hex id). | Pass a valid realm selector. | ```json { "code": "forbidden", "message": "request must arrive through the Hoody proxy" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `forbidden` | Forbidden (Source IP Guard) | The request did not come through the program's URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with `hoody agent …`. | ```json { "code": "not_found", "message": "resource not found" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `not_found` | Not found | The requested resource does not exist. | Verify the path and identifier. | ```json { "code": "payload_too_large", "message": "request body exceeds the configured size limit" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `payload_too_large` | Payload too large | The request body exceeds the configured size cap (`MaxBodyBytes`). | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. | ```json { "code": "rate_limited", "message": "request rate limit exceeded" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `rate_limited` | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the `Retry-After` header and retry; reduce the request rate. | ```json { "code": "internal_error", "message": "internal server error" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `internal_error` | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. | ```json { "code": "service_unavailable", "message": "service unavailable" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `service_unavailable` | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor `Retry-After` and retry. | ### `PATCH /api/v1/agent/sessions/{id}/attachments/{lease_id}` Renew an attachment lease. Extends the lease's expiry, decided atomically against the reaper's close (`404` once closing). A lapsed lease is refused with `410 attachment_expired` (acquire a new one). `404` is also returned when the lease is unknown, not this session's, or not this owner's. Renewals are re-authorized against the session's current owner and realm, so a lease never outlives a changed pin or a credential switch. #### Parameters | Name | In | Type | Required | Description | |------|-----|------|----------|-------------| | `id` | path | string | Yes | Session identifier. | | `lease_id` | path | string | Yes | Attachment lease identifier. | | `X-Hoody-Cwd` | header | string | No | Per-request working-directory scope. | | `X-Hoody-Config-Dir` | header | string | No | Per-request `--config-dir` override. | | `X-Hoody-Container` | header | string | No | Per-request bound remote container. | | `X-Hoody-Realm` | header | string | No | Per-request realm selector. | | `realm` | query | string | No | Per-request realm selector (in-query alias). | #### Request body | Field | Type | Required | Description | |-------|------|----------|-------------| | `ttl_ms` | integer | No | New lifetime in milliseconds from now (default 15m, capped at 60m). | #### Request example ```bash curl -X PATCH "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/sessions/{id}/attachments/{lease_id}" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"ttl_ms": 900000}' ``` ```ts import { HoodyClient } from 'hoody-sdk'; const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN }); await client.agent.sessions.renewAttachment('{id}', '{lease_id}', { ttl_ms: 900000 }); ``` #### Responses ```json { "lease_id": "atl_01HF3ZQ4MX9JQ7WZK6S3OAE5E2", "expires_at": "2025-03-19T14:52:08.314Z" } ``` | Field | Type | Description | |-------|------|-------------| | `lease_id` | string | The lease id. | | `expires_at` | string | RFC3339 expiry; renew before it or the session may be reaped once idle. | ```json { "code": "bad_request", "message": "invalid request" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `bad_request` | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. | | `realm_scope_unsupported` | Realm scope unsupported | A per-request realm header was supplied to an active-only / global-no-realm RPC, which has no realm dimension to scope. | Omit the realm header on this route, or open a session to scope by realm. | | `invalid_realm` | Invalid realm selector | The realm selector is malformed (not ""/"global"/a 24-hex id). | Pass a valid realm selector. | ```json { "code": "forbidden", "message": "request must arrive through the Hoody proxy" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `forbidden` | Forbidden (Source IP Guard) | The request did not come through the program's URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with `hoody agent …`. | ```json { "code": "not_found", "message": "resource not found" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `not_found` | Not found | The requested resource does not exist. | Verify the path and identifier. | ```json { "code": "attachment_expired", "message": "the attachment lease expired; acquire a new one" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `attachment_expired` | Attachment lease expired | The attachment lease already lapsed; an expired lease is not revived by renewal. | Acquire a new lease (`POST /sessions/{id}/attachments`). | ```json { "code": "payload_too_large", "message": "request body exceeds the configured size limit" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `payload_too_large` | Payload too large | The request body exceeds the configured size cap (`MaxBodyBytes`). | Reduce the request body below the configured limit (default 8 MiB); split a large payload into smaller requests. | ```json { "code": "rate_limited", "message": "request rate limit exceeded" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `rate_limited` | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the `Retry-After` header and retry; reduce the request rate. | ```json { "code": "internal_error", "message": "internal server error" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `internal_error` | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. | ```json { "code": "service_unavailable", "message": "service unavailable" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `service_unavailable` | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor `Retry-After` and retry. | ### `DELETE /api/v1/agent/sessions/{id}/attachments/{lease_id}` Release an attachment lease. Drops the lease; the session resumes ordinary idle-reap behaviour. `404` is returned when the lease is unknown, not this session's, or not this owner's. #### Parameters | Name | In | Type | Required | Description | |------|-----|------|----------|-------------| | `id` | path | string | Yes | Session identifier. | | `lease_id` | path | string | Yes | Attachment lease identifier. | | `X-Hoody-Cwd` | header | string | No | Per-request working-directory scope. | | `X-Hoody-Config-Dir` | header | string | No | Per-request `--config-dir` override. | | `X-Hoody-Container` | header | string | No | Per-request bound remote container. | | `X-Hoody-Realm` | header | string | No | Per-request realm selector. | | `realm` | query | string | No | Per-request realm selector (in-query alias). | This endpoint accepts no request body. #### Request example ```bash curl -X DELETE "https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com/api/v1/agent/sessions/{id}/attachments/{lease_id}" \ -H "Authorization: Bearer " ``` ```ts import { HoodyClient } from 'hoody-sdk'; const client = new HoodyClient({ baseURL: 'https://{projectId}-{containerId}-agent-1.{server}.containers.hoody.com', token: process.env.HOODY_TOKEN }); await client.agent.sessions.releaseAttachment('{id}', '{lease_id}'); ``` #### Responses ```json { "status": "ok", "released": true } ``` | Field | Type | Description | |-------|------|-------------| | `status` | string | `"ok"` on success. | | `released` | boolean | Always true on a 200. | ```json { "code": "bad_request", "message": "invalid request" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `bad_request` | Bad request | The request was malformed or carried invalid parameters. | Correct the request body or query parameters. | | `realm_scope_unsupported` | Realm scope unsupported | A per-request realm header was supplied to an active-only / global-no-realm RPC, which has no realm dimension to scope. | Omit the realm header on this route, or open a session to scope by realm. | | `invalid_realm` | Invalid realm selector | The realm selector is malformed (not ""/"global"/a 24-hex id). | Pass a valid realm selector. | ```json { "code": "forbidden", "message": "request must arrive through the Hoody proxy" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `forbidden` | Forbidden (Source IP Guard) | The request did not come through the program's URL. Hoody Kit programs are reached through their URLs only. | Call the agent through its URL, for example with `hoody agent …`. | ```json { "code": "not_found", "message": "resource not found" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `not_found` | Not found | The requested resource does not exist. | Verify the path and identifier. | ```json { "code": "rate_limited", "message": "request rate limit exceeded" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `rate_limited` | Too many requests | The per-client request rate limit was exceeded; the gateway throttled the request before dispatch. | Honor the `Retry-After` header and retry; reduce the request rate. | ```json { "code": "internal_error", "message": "internal server error" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `internal_error` | Internal error | An unexpected error occurred while handling the request. | Retry; if persistent, inspect the daemon logs. | ```json { "code": "service_unavailable", "message": "service unavailable" } ``` | Error Code | Title | Description | Resolution | |------------|-------|-------------|------------| | `service_unavailable` | Service unavailable | The daemon could not service the request (too busy, or a per-client stream concurrency cap was hit). | Honor `Retry-After` and retry. | ## See also - [Agent: Session approvals](/api/agent/sessions/approvals/) for the gates an approver lease lets you answer.