Outside agents: the gateway, actions API, MCP and SDKs

Last updated October 5, 2026

On this page

Draft for review

An outside agent is an agent your company built somewhere else: on LangGraph, the OpenAI Agents SDK, Copilot Studio or its own code. It does not run inside OrchKernel. It reaches your company's tools and data only through the gateway: it asks for one action at a time, and each request goes through the same checks (the gate), the same approvals and the same event log as an action by one of OrchKernel's own agents (a kernel agent, see Agents). The kernel then carries the action out with its own connections, so the outside agent never holds a vendor key or password.

This page covers registering an outside agent, giving it a token, what it can ask for, how approvals, expiry and callbacks work, the MCP endpoint, the limits, offboarding, and the TypeScript and Python clients. It describes the gateway as it works today on the golive branch. What is not possible yet is listed at the end.

How an outside agent differs from a kernel agent#

Kernel agent Outside agent
Where it runs Inside OrchKernel, on its playbook and skills In your own runtime
How it acts Plans steps that the kernel runs Asks for each action through POST /api/actions or /mcp
What it can reach The tools of its skills, within delegations Its ceiling: the tools, collections and data classes picked at registration, within delegations
Credentials None; the kernel calls vendors None; the kernel calls vendors
Trust Up to trusted Sandboxed (default) or standard, never higher
Can be assigned tasks, mentioned in threads, asked from a conversation, put in a module Yes No
Its page in the Directory Overview, Playbook, History, Scorecard Overview, Gateway, Tokens, Scorecard

A delegation is a person's grant to an agent: who it acts for, what it may read and use, and how much it may spend (see Delegations). A data class is a label on data, such as pii.customer for customer personal data; picking it at registration lets that data leave to the agent's runtime.

An outside agent cannot be hired, forked, installed with a pack or a module, or created through the change pipeline. Only a human admin registers one.

Before you start in the demo#

The steps on this page were run in the demo company (see Quickstart): ok demo, then ok demo serve, signing in with the tokens it prints. The curl examples use http://127.0.0.1:8080, the default address; use the Workspace address ok demo serve printed if you started it with --bind.

No model is needed for anything on this page: the outside agent brings its own, and you play the agent with curl.

Two things to know:

  • Callbacks need OK_SECRETS_KEY. ok demo serve does not set one, so registering with a callback URL ends with "No callback secret: the server has no OK_SECRETS_KEY. Set it, then issue one from the agent page." To try callbacks, stop the demo and start it again with a key. Every start issues new sign-in tokens and revokes the old ones, agent tokens included, so issue the agent a new token afterwards.

    sh
    export OK_SECRETS_KEY=$(openssl rand -base64 32)
    ok demo serve
    
  • A plain http callback URL works only to loopback in development mode. ok demo serve runs in development mode, so http://127.0.0.1:<port>/... is accepted from the registration sheet. Everywhere else the callback URL must be https.

Registering an outside agent#

Sign in as ada (the admin).

  1. Click Directory, then Register an outside agent (top right; only admins see it). A sheet opens: "An agent built elsewhere acts only through the gateway: ..."
  2. Fill in: Id support-bot, Name Support bot, Role support agent, Runtime LangGraph, Team Support. Under Acts for tick Tom Becker (tom). Under Tools tick support-ops.helpdesk.reply and support-ops.helpdesk.tickets. Under Reads tick tickets.
  3. Under Data classes the sheet now offers pii.customer (restricted), the class those tools and that collection carry. Tick it. A red line appears: "Data of class pii.customer is restricted: it will leave to the agent's runtime." That is a warning, not a refusal.
  4. Callback URL http://127.0.0.1:18931/hooks/kernel (optional; nothing needs to listen there for this walkthrough), First token lives 7d, Pin the first token to Tom Becker.
  5. Choose Register. The sheet now reads "Registered support-bot" with "Copy them now: each is shown once." and the Agent token (orchk_ag_ and 64 hex digits) with a Copy button, then "Token , expires , pinned to tom." In the demo it also says there is no callback secret (see above), and a yellow badge repeats the restricted data warning. Copy the token, then choose I have copied it.

The agent now appears in the Directory with an Outside: LangGraph badge.

Field What it does
Id, Name, Role Required. The id is the agent's address; it cannot change later.
Runtime Free text shown on its page and on approval cards ("Outside agent: LangGraph").
Description Free text.
Team, Reports to At least one is required: "Pick a team or a manager, so a manager approval has a person." The team's lead sees the agent's actions. A manager who is not the team lead gets no inbox items for it and does not see its Gateway tab; the sheet says so when you pick one.
Trust sandboxed (default) or standard.
Acts for People and teams. Each gets a delegation to the agent (a team: each member). The agent can act only for people who delegated to it.
Tools What it may call. Built-in kernel tools are not offered.
Reads, Writes Collections it may query, and write. A collection ticked under Writes is also read. System collections (modules, views) are not offered.
Data classes The data classes that may leave to the agent. Nothing is ticked by default, and the list offers only the classes the chosen tools and collections carry. A tool or collection whose class is not ticked is left out of the ceiling.
Identities Greyed out: identities need an agent identity provider, which is not available yet.
Callback URL Where the kernel posts status changes. Optional; without it the agent polls.
Approvals expire after Default 24h.
First token lives, Pin the first token to The first token's lifetime (empty means the agent's cap, 30 days) and the one person it acts for. Only people ticked under Acts for are offered.

A problem with the registration comes back as one list, and nothing is created. For example, an unknown tool, a write collection that is not read, a trust above standard and an identity together give: "tool no.such.tool does not exist; write_collections must be a subset of read_collections: tickets; trust must be sandboxed or standard for an outside agent; identities need an agent issuer, which is not available yet".

From the command line#

The same registration as a file, for ok gateway agent add:

yaml
id: support-bot
name: Support bot
role: support agent
runtime: LangGraph
team: team:support
tools: [support-ops.helpdesk.tickets, support-ops.helpdesk.reply]
read_collections: [tickets]
data_classes: [pii.customer]
acts_for: [tom]
callback_url: https://agents.example.com/hooks/kernel
limits: { actions_per_min: 60, pending_max: 20 }
approval_ttl: 24h

The CLI works on the state file directly, so stop the demo server first, run the commands from the directory where you ran ok demo, and name the state file and the admin:

sh
S="--state orchkernel-demo/state.db --as ada"
ok gateway agent add support-bot.yaml $S                 # prints warnings, then the agent
ok gateway token issue support-bot --pin tom --ttl 7d $S  # prints the token once
ok gateway callback rotate support-bot $S                 # prints the secret once; needs OK_SECRETS_KEY
ok gateway agent set support-bot '{"approval_ttl":"2h"}' $S

Without --state and --as the CLI looks for ./.orchkernel/state.db and your login name, and fails with "unknown actor ". The command line is never in development mode, so it refuses an http callback URL even to loopback ("the URL scheme must be https (plain http is allowed only to a loopback IP literal in dev mode)"). Only human admins run these; Maya gets "only human admins manage outside agents" from agent add and "only human admins issue agent tokens" from token issue.

Optional settings not on the sheet: budget (a spending cap of the agent's own; without one it has none, though delegation, team and work session budgets still apply), limits (see Limits), token_max_ttl_days (default 30, at most 90) and allowed_cidrs (addresses the agent may call from; empty means any).

The agent in the Directory#

Click Support bot in the Directory. The details show its facts, Let Support bot act for me, Acts under (Tom's delegation: "registered to act for this person through the gateway") and, for admins, Promote to standard and Offboard. Promoting to trusted is refused ("Support bot runs outside OrchKernel; the trusted tier is not possible").

Open agent page (address /directory/support-bot) has four tabs. The header shows Outside agent: LangGraph and its state (Active or Offboarded).

Tab What it shows Who sees it
Overview Id, runtime, role, team, reports to, trust, identities, tools, reads, writes, data classes, callback URL and callback health, approval expiry ("after 1 days"), token lifetime cap ("at most 30 days"), allowed addresses ("any"), limits ("60 a minute, 5000 a day, 20 waiting, 30 approvals an hour, 120 polls a minute") and version. Admins also get Issue callback secret (or Rotate callback secret once one exists) and Send a test. Anyone who opens the page. Callback health is accurate only for admins (see Known problems).
Gateway Every action with a status filter (all, received, executing, pending approval, executed, failed, denied, rejected, expired, withdrawn, unknown): status, action (tool.call(support-ops.helpdesk.reply)), acting for, the decision ("allow", "require approval at policy", "deny at availability"), asked, finished and its run. Pending actions show their approval card underneath. Then Work sessions. Admins and the lead of the agent's team. Others see "Only admins and the agent's team lead see its actions."
Tokens Each token's id, source ("admin (ada)"), pinned to, issued and expires, with Revoke. Under the table a lifetime box, a Pin to list and Issue token. Admins. Everyone else sees "No tokens."
Scorecard Actions by status ("executed 6 of 13 actions") and Approval latency: how many asked for approval, and the median and longest time from asking to a decision. Admins and the team lead. Others see "Only admins and the agent's team lead see its scorecard."

To change settings later there is no form yet. Use ok gateway agent set (a JSON merge patch, above) or PUT /api/gateway/agents/:id with the whole registration and the version you read (see For developers). Trust is kept as it is: change it with Promote to standard. Changing the callback URL clears the callback secret.

Tokens#

The agent sends its token as Authorization: Bearer orchk_ag_....

  • Issue from the Tokens tab: type a lifetime such as 1d, optionally pick a person under Pin to, and choose Issue token. A sheet "Token for Support bot" says "Copy it now: it is shown once." (and, when pinned, "Every action with it acts for alice."). Or use ok gateway token issue. A token lives at most the agent's cap (30 days by default); asking for more is refused ("ttl must be at most 30 days for support-bot").
  • Pin a token to one person when the agent does one person's work. Every action with it acts for that person; naming anyone else is refused with 403 pinned ("this token acts for one person only"). You can pin only to someone who has delegated to the agent.
  • Revoke: choose Revoke on the token's row, then Revoke in the dialog "Revoke token f69a8ea7? The agent can no longer use it." A note says "Revoked f69a8ea7". The next request with it gets 401 "invalid token".

An agent token works only on the agent's routes: POST /api/actions, GET /api/actions, GET /api/actions/:id, POST /api/actions/:id/cancel, /api/gateway/work-sessions..., GET /api/gateway/tools and /mcp. Anywhere else it gets 403 agent_token_scope ("an agent token works on the agent-facing gateway routes only"). The other way round, a person's token cannot ask for an action: POST /api/actions with Tom's token is 403 not_external_agent ("this route serves outside agents' tokens only").

Who the agent acts for#

Every action acts for one person. The kernel picks that person from, in order:

  1. the token's pin;
  2. the request's acting_for (on MCP, the x-ok-acting-for header sent with initialize);
  3. the only person who has delegated to the agent, if there is exactly one.

Otherwise the request is refused with 422 acting_for_required ("name the person this request acts for in acting_for"). The person is never taken from the action's arguments or the agent's reason.

More people can let the agent act for them after registration: open the agent in the Directory and choose Let Support bot act for me. A note says "Support bot now acts for you". That person's delegation then bounds what the agent may do for them. Try it as alice: afterwards an unpinned token with no acting_for is refused with acting_for_required, because two people now delegate to the agent.

Be careful who you let an outside agent act for: today it can read data the person it acts for may not read (see Known problems).

Asking for an action#

The agent posts to POST /api/actions:

json
{
  "idempotency_key": "reply-hd4826-1",
  "action": { "kind": "tool", "tool": "support-ops.helpdesk.reply",
              "args": { "ticket": "HD-4826", "body": "Your refund is on its way." } },
  "reason": "Customer asked about the duplicate charge refund",
  "wait": true
}
Field What it does
action.kind tool (with tool and args), query or aggregate (with collection and a query), or upsert (with collection, fields and an optional id). Nothing else can be asked.
idempotency_key Required (400 idempotency_key_required without it): 1 to 128 letters, digits and . _ : -. Sending the same request again with the same key answers the first action (200, header idempotent-replayed: true) and runs nothing. The same key with a different request is 409 idempotency_mismatch ("this idempotency key was used with another request"). A key stays in use while its action is open and for 24 hours after.
acting_for The person, when the token is not pinned.
reason Shown to the approver under "The agent says:". It is treated as data, never as instructions.
wait true (default) answers once the action has run or is waiting. false answers as soon as the gate has decided.
work_session Optional; see Work sessions.

The answer is the action itself: its id, status, version, who it acts for, the decision with its steps, the approval (approver's name and expiry) when it waits, the error when it did not run, and the result once it ran.

Status Meaning
executing Allowed and running.
pending_approval Waiting for a person.
executed Done; result holds the answer.
failed The tool or write failed; error says why.
denied The gate or a limit refused it. Nobody was asked.
rejected The approver said no. Their note is passed on.
expired Nobody decided in time.
withdrawn Cancelled by the agent, or the agent was offboarded, or the person's delegation was revoked.
unknown The server restarted while the action was running; the vendor may or may not have done it.

denied, rejected, expired and withdrawn are normal answers (HTTP 201), not errors.

What the agent learns of a decision#

The decision's steps are rewritten for the agent: each check, its result and a short line ("acting for Tom Becker under their delegation", "Maya Patel would be asked"). A matched policy rule keeps its id and description. The agent never sees a budget figure, a team, a module, a connection or the rule's conditions.

A tool the agent may not use and a tool that does not exist get the same answer: denied at stage availability, "tool no.such.tool is not available to this agent".

A tool's argument rules are checked before anyone is asked. The helpdesk reply goes only to a ticket read earlier in the same work session (see Work sessions); a reply to any other ticket is denied at stage arguments with only "an argument breaks the tool's rules", without saying which rule.

Try it in the demo#

With the support bot registered and its pinned token in $TOKEN:

  1. Read the open tickets:

    sh
    curl -s http://127.0.0.1:8080/api/actions \
      -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' \
      -d '{"idempotency_key":"tickets-open-1","action":{"kind":"tool",
           "tool":"support-ops.helpdesk.tickets","args":{"status":"open"}},
           "reason":"Find open tickets to answer"}'
    

    You get "status": "executed", "acting_for": "tom" and a result with two tickets, HD-4821 "CSV export fails since Monday" and HD-4826 "Refund for duplicate charge". Add -i and send the same command again: 200 with idempotent-replayed: true and the same action.

  2. Ask for something outside the ceiling, such as support-ops.billing.refund (with a new idempotency key): "status": "denied", "tool support-ops.billing.refund is not available to this agent".

  3. Reply to HD-4826 with the reply tool (the JSON above). The answer is "status": "pending_approval" with "approver": "Maya Patel" and an expiry 24 hours away. The decision's reason reads "rule support-ops-customer-replies-need-the-lead: Agents never reply to a customer without the support lead approving the draft".

Approvals and expiry#

An action that needs approval waits and asks the person the policy names; by default that is the person the agent acts for. In the demo the Support operations module's rule names the support lead, so the support bot's reply for Tom goes to Maya, not Tom. See Inbox and approvals.

  1. Sign in as maya and open Inbox. Under Waiting on you: Reply on a ticket, badges Outside agent: LangGraph and Pending, "Support bot asks to reply on a ticket. acting for Tom Becker", "The arguments, exactly as they will run:", "The agent says: Customer asked about the duplicate charge refund", "Expires in 24 h", the rule's description, See the run, Why am I asked?, Reject and Approve. The same card shows on the agent's Gateway tab.
  2. Choose Approve. A note says "Approved" and the card leaves the inbox.
  3. Fetch the action (GET /api/actions/<id> with the agent's token). It is executed with a result such as {"id": "reply-90c1e2"}.

Only the named approver can decide. Tom trying to approve through the API gets 403 not_approver. To reject, choose Reject, type a reason under Reason (optional) and choose Confirm reject; a note says "Reply on a ticket: rejected · Tom Becker was told". The reason goes back to the agent; over MCP it reads "Rejected: Say when exactly the refund lands".

Approving checks the policy again first, and two approvers at once, an approval racing the expiry, or a restart mid-call never run the action twice.

  • Expiry. Nobody deciding within the agent's approval time (24 hours by default) ends the action expired with "the approval expired before anyone decided it". With a 5-minute expiry, the action turned expired a few seconds after its deadline.
  • Withdrawal. The agent cancels with POST /api/actions/:id/cancel (status withdrawn, error "withdrawn: cancelled"). Offboarding the agent, or the person revoking their delegation, withdraws their waiting actions too.

Who can read an action and its result#

Who Reads the action Gets the result
The agent itself Yes Yes
The person it acted for (Tom) Yes, with their own token Yes
The approver, the agent's team lead (Maya), admins (Ada) Yes No. An admin can ask ?result=true, which is recorded as an "Outside agent read a result" event.
Anyone else (Alice, Erin) 404 No

Results are kept 24 hours after the action ends; arguments and the reason 30 days (the operator can change the second, see Limits).

Callbacks#

An agent with a callback URL gets a POST each time one of its actions changes status, so it does not need to poll. Deliveries go out from one worker with retries after 30 seconds, 2 minutes, 10 minutes, 1 hour and 6 hours.

  • Secret. Issued at registration when the server has OK_SECRETS_KEY, or later with Issue callback secret / Rotate callback secret on the Overview tab or ok gateway callback rotate. It starts with whsec_ and is shown once ("Copy it now: it is shown once. The secret it replaces no longer works."). Rotating does not ask you to confirm. Without OK_SECRETS_KEY the button answers "a secret cannot be stored: OK_SECRETS_KEY is not set".
  • Test. Send a test on the Overview tab (or ok gateway callback test) sends a signed {"type":"test"} and says "Test delivered" or why it failed ("Test failed: Connection Failed: Connect error: Connection refused"). With no secret yet it says "support-bot has no callback secret; issue one first".

A delivery looks like this:

http
POST /hooks/kernel
x-ok-signature-version: 2
x-ok-timestamp: 1791198929
x-ok-delivery: dlv_90ce8f25ddc644118b45806c74df43dd
x-ok-signature: sha256=7c155e38...

{"action":"act_948f72c61fecfb211f66d872e6b864db","agent":"support-bot",
 "at":"2026-10-05T11:15:29.636Z","status":"pending_approval",
 "type":"action.updated","version":2,"work_session":"ws_ac948c..."}

The body never carries a result or the decision's text; fetch the action for those. Every status change is delivered, including executing, denied and withdrawn.

Verify every delivery. The signature is HMAC-SHA256, keyed with the whole callback secret (whsec_...), over <timestamp>.<delivery>.<raw body>. Compare it in constant time, refuse timestamps more than a few minutes old, and remember delivery ids to refuse replays. The SDK functions below do all three: version 2 only, within five minutes, and no delivery id seen in the last ten minutes.

Work sessions#

A work session groups an agent's actions for one person into one kernel run, so the person, the agent's team lead and admins can follow it on the Work page ("Support bot through the gateway"). An action that names no work session goes into the agent's default one for that person (shown as default on the Gateway tab).

  • Open one with POST /api/gateway/work-sessions and {"acting_for": "tom", "name": "Refund follow-ups", "budget": {"limit": 500}} (the budget in cents, optional); end it with POST /api/gateway/work-sessions/:id/end.
  • An agent holds at most 20 open work sessions; opening a 21st closes its longest-idle one, if that has been idle 5 minutes or more.
  • A work session closes by itself after 30 idle minutes or 24 hours in all.
  • Each MCP session is a work session, named "MCP: ".
  • What a work session read decides what some tools may do next: a helpdesk reply in a new MCP session is denied until that session has read the tickets.

MCP#

Any MCP client can use the agent's token against /mcp at the server's root (not under /api). It speaks MCP over Streamable HTTP (protocol versions 2025-06-18 and 2025-03-26), one JSON answer per request.

text
url:     http://127.0.0.1:8080/mcp
headers: Authorization: Bearer orchk_ag_...
         x-ok-acting-for: tom      # only when the token is not pinned

What a client sees:

  • initialize opens a work session and answers its id in the Mcp-Session-Id header, which every later request must carry (missing: 400 session_required; unknown or ended: 404 session_not_found). With no person to act for it answers error -32602 "acting_for_required: send x-ok-acting-for". The server's instructions say: "Every call is governed. A call that needs a person's approval returns 'waiting for approval' with an action id. Call kernel_action_status with that id later to get the result."
  • tools/list lists the ceiling for that person, with dots in tool ids turned into __ (support-ops__helpdesk__reply) and the real id in _meta["orchkernel/tool"], plus the kernel's own tools: kernel_collections, kernel_query, kernel_aggregate, kernel_upsert (only when a collection is writable, so not for the support bot) and kernel_action_status. _meta["orchkernel/may_need_approval"] is a hint only. readOnlyHint is true only for tools at risk read with no side effect, so the demo's tickets tool (risk low) is not marked read-only.
  • tools/call is a governed action. An executed call returns the result. A call that needs approval returns isError: true with "Not done yet: waiting for approval from Maya Patel (action act_..., expires 2026-10-06 12:07 UTC). Call kernel_action_status with this action id to get the result." Nothing failed: call kernel_action_status with { "action": "act_...", "wait_s": 20 } (at most 30) to wait for it. A refused call returns isError: true with the reason, such as "Not allowed: an argument breaks the tool's rules".
  • DELETE /mcp ends the session (204).

Not served: resources, prompts, sampling, elicitation, roots, completions, logging and server-initiated messages. GET /mcp is 405; a request with no token is 401 with WWW-Authenticate: Bearer.

Limits#

Per agent, under limits at registration (the Overview tab shows them):

Limit Default Bounds Over it
actions_per_min 60 1 to 600 429 rate_limited with Retry-After: 60 ("actions_per_min reached; retry after 60 seconds")
actions_per_day 5,000 1 to 100,000 The same
pending_max 20 1 to 200 denied at stage limits, "pending_max is reached: no more actions may wait for approval now"; nobody is asked
approvals_per_hour 30 1 to 500 denied at stage limits
polls_per_min 120 10 to 1,200 429 on reads of actions, the tool list and kernel_action_status

Opening a work session (or an MCP initialize) counts as one action against the per-minute and per-day limits. The agent's own budget, the delegation's per-action cap, the team's and the work session's budgets apply as for kernel agents.

For the operator, per server:

Variable Default What it sets
OK_GATEWAY_MAX_EXECUTING 64 Gateway actions running at once; over it, 429.
OK_GATEWAY_MAX_WAITS 1,000 Long polls and kernel_action_status waits held at once.
OK_GATEWAY_KEEP_ARGS_DAYS 30 Days an action's arguments and reason are kept after it ends (1 to 365).
OK_GATEWAY_KEEP_DAYS 400 Days an action's row and callback records are kept.
OK_CORS_ORIGINS unset Browser origins /mcp serves; agents send none.
OK_TRUSTED_PROXIES unset Proxies whose X-Forwarded-For is trusted for allowed_cidrs.

A client address gets 429 after 60 failed sign-ins in a minute.

Offboarding#

As ada, in the Directory, click the agent, then Offboard, and confirm with Offboard in the dialog "Offboard Support bot? All delegations are revoked and running work cancelled." A note says "Offboarded". Then:

  • its tokens stop working (401 "invalid token");
  • waiting actions become withdrawn (error "withdrawn: offboarded"), and the agent gets that callback;
  • its work sessions close (reason offboarded);
  • its callback secret is cleared.

Its page, now marked Offboarded, and its history stay readable.

The threat model in brief#

  • The agent's runtime and model are outside the trust boundary. They get filtered decision steps, never a budget figure, a connection or a credential. Their arguments and reason are data, never instructions.
  • A stolen agent token reaches only the agent routes, within the ceiling, for its pinned person, from allowed_cidrs, until it expires or is revoked.
  • Nothing runs twice: idempotency keys, a row claimed before any vendor call, and a vendor idempotency key derived from the action id.
  • The event log records who asked, for whom, the decision, the approver and the outcome; never arguments, results, the reason, a token or a callback secret. See Events and the audit log.

On the Events page, the Type list's Sign-in and outside agents group holds Token issued, Token narrowed, Token revoked, Outside agent changed, Work session opened, Work session closed, Outside agent asked to act, Outside action decided, Outside action finished, Callback sent, Outside agent read a result and Outside agent rate limited.

Known problems#

Found while writing this page:

  • An outside agent can read data the person it acts for cannot. In the demo, Alice (sales) is refused the tickets collection ("alice may not read tickets"), but after she chooses Let Support bot act for me, the support bot acting for her reads all five tickets with a query action. The gateway checks the agent's ceiling and Alice's delegation, not Alice's own access.
  • The delegation shows an absurd spending limit. In the Directory details, Acts under reads "up to $23,058,430,092,136,940.00" for a delegation with no cap.
  • For the team lead (Maya), the Overview tab says "callback health: no delivery yet" even after deliveries. The Tokens tab says "No tokens." to everyone but admins, even when tokens exist, instead of saying only admins see them.
  • The Tokens tab's Pin to list offers every person; pinning to someone who has not delegated to the agent is refused ("pin alice must be an active human holding a delegation to support-bot").
  • The Overview tab reads "approvals expire after 1 days".
  • The decision steps list "budget: the budgets allow this call" twice.
  • A withdrawn action's decision on the Gateway tab reads "cancelled: withdrawn: cancelled".
  • A reply refused by an argument rule tells the agent only "an argument breaks the tool's rules", not which rule or how to fix it.
  • The Directory details do not refresh after Let Support bot act for me; close and reopen them to see the new delegation.

Not possible yet#

  • Agent identity providers: no Entra or Okta tokens, no token exchange, no identities on a registration (refused with 422). An admin issues every agent token.
  • Editing a registration from the screen (use ok gateway agent set or the API).
  • Assigning an outside agent a task, mentioning it in a thread, asking it from a conversation, or putting it in a module or pack.
  • Trust above standard.
  • Advisory mode (refused: "advisory mode is not available").
  • Changing who the agent acts for after registration, other than people delegating to it themselves.

For developers#

The clients#

Both clients live under sdk/ in the source tree: sdk/typescript (Node 18+, Deno, Bun; no dependencies; build with npm install && npm run build) and sdk/python (Python 3.9+, standard library only; pip install ./sdk/python). Both refuse plain http except to loopback, never follow redirects, generate an idempotency key when you give none and retry a network error with the same key, and return denied, rejected, expired and withdrawn as statuses, not exceptions.

TypeScript: ask, wait for the decision, verify a callback.

ts
import { Client, isFinal, verifyCallback } from 'orchkernel'

const ok = new Client({ baseUrl: 'http://127.0.0.1:8080', token: process.env.OK_AGENT_TOKEN! })

const a = await ok.actions.create({
  tool: 'support-ops.helpdesk.reply',
  args: { ticket: 'HD-4821', body: 'We fixed the CSV export.' },
  reason: 'Customer reported the CSV export bug',
})
// a.status === 'pending_approval', a.approval.approver === 'Maya Patel'
const done = isFinal(a) ? a : await ok.actions.wait(a.id, { timeoutMs: 10 * 60_000 })
console.log(done.status, done.result)          // executed { id: 'reply-...' }

// In your callback handler, with the raw body as received:
const event = await verifyCallback(secret, request.headers, rawBody)

Python: the same, plus a callback receiver.

python
import os
from orchkernel import Client, is_final, verify_callback, CallbackError

secret = os.environ["OK_CALLBACK_SECRET"]

ok = Client("http://127.0.0.1:8080", token=os.environ["OK_AGENT_TOKEN"])
a = ok.actions.create(
    tool="support-ops.helpdesk.reply",
    args={"ticket": "HD-4826", "body": "Your refund is on its way."},
    reason="Customer asked about the duplicate charge refund",
)
done = a if is_final(a) else ok.actions.wait(a["id"], timeout=600)

def handle(headers, raw_body):          # in your HTTP server
    try:
        event = verify_callback(secret, headers, raw_body)
    except CallbackError as e:          # bad_signature, stale_timestamp, replayed, ...
        return 401
    if event.get("type") == "action.updated":
        action = ok.actions.get(event["action"])

These replies go into the agent's default work session for the person, so they pass the reply's argument rule only after that session read the tickets (step 1 of Try it in the demo).

Also: actions.query, actions.aggregate, actions.upsert, actions.get (with after_version and wait for a long poll), actions.list, actions.cancel, work_sessions.open / workSessions.open, get, end, and tools(). wait gives back the latest state when the timeout passes first; check it with is_final / isFinal.

Agent routes#

All under /api with the agent token, except /mcp.

Method and path Notes
POST /actions Ask for an action (above). 201 new, 200 replayed.
GET /actions The agent's actions, newest first; status, work_session, limit, cursor in x-next-cursor.
GET /actions/:id?after_version=N&wait=30 Holds the request until the version passes N, at most 30 seconds.
POST /actions/:id/cancel Withdraw a waiting action.
GET /gateway/tools?acting_for= The ceiling for that person, with each tool's argument schema, risk and side effect.
POST /gateway/work-sessions, GET /gateway/work-sessions[/:id], POST /gateway/work-sessions/:id/end Work sessions.
POST, DELETE /mcp MCP.

Admin routes#

With a person's token. Admins, and for reads the agent's team lead.

Method and path Notes
GET, POST /gateway/agents List and register. 201 with actor and warnings; 422 invalid with problems.
GET, PUT /gateway/agents/:id Read (actor, version, callback), replace. PUT takes the whole registration plus the version read: without it 428 version_required, stale 409 version_conflict. Fields left out go back to their defaults.
POST /gateway/agents/:id/tokens { ttl?, pin? }; the token is in the answer once.
POST /gateway/agents/:id/callback-secret, .../callback-test Issue or rotate (503 secrets_key_missing without OK_SECRETS_KEY); send a test (409 no_callback_secret before a secret exists).
GET /gateway/agents/:id/actions, .../work-sessions The agent's actions and work sessions.
DELETE /tokens/id/:token Revoke one token.
POST /approvals/:id { approved, note? }, by the named approver.
POST /actors/:id/trust { tier: "standard" }; trusted is 409 external_agent.

ok gateway actions --agent support-bot and ok gateway work-sessions --agent support-bot list the same from the command line (with --state and --as as above), without results.

Errors#

Status and code When
400 idempotency_key_required No idempotency key.
401 unauthorized Unknown, expired or revoked token.
403 agent_token_scope An agent token outside the agent routes.
403 not_external_agent A person's or API token asking for an action.
403 pinned A pinned token naming another person.
403 not_approver Someone other than the named approver deciding.
409 idempotency_mismatch A key reused with a different request.
409 now_denied Approving an action the policy now refuses.
422 acting_for_required No person could be chosen.
422 invalid A bad registration, token request or idempotency key, with problems.
429 rate_limited Over a per-minute or per-day limit, with Retry-After.

See also The gate, Delegations, Policy and Inbox and approvals.