Operation
Operations: work that outlives a request (a human's answer, a connect
link, an agent turn). op.wait() returns the typed outcome for its kind.
class OperationA long-running piece of work. Block on it with wait().
- Like every live object, it also has hydrate(), refresh() and raw.
| id | str | |
| kind | str | |
| status | str | |
| step | str | None | Where a running operation is. A turn of an agent on a CLI harness: |
| result | dict[str, Any] | None | The raw result once succeeded. |
| created_at | Any | |
| updated_at | Any | |
| summary_text | str | None | A short line saying what it is (a message preview, an addressee, a command). |
| user_id | str | None | |
| key_id | str | None | The key that started it. |
| agent_id | str | None | |
| input | dict[str, Any] | None | What it was started with (single-operation views, account principals). |
| started_at | Any | |
| finished_at | Any | |
| wait_ms | int | None | Time queued before it ran. |
| run_ms | int | None | Time running. |
| held_ms | int | None | Time waiting on a person. |
| progress | dict[str, Any] | None | What the work reported as it went, or |
| undoable | bool | None | Whether |
| undone_at | Any | When it was undone, if it was. |
| transitions | list[OperationTransition] | Every status change, oldest first (empty for operations recorded before they were kept). |
| console_url | str | None | This operation in the developer console (None off the hosted API). |
| error | Error | None | The failure as an |
| done | bool | Whether the operation reached a terminal status. |
listclassmethodasync
noclick.Operation.list(
*, kind=None, status=None, status_not=None, agent=None, key=None,
created_after=None, created_before=None, updated_after=None, limit=None,
user_filter=None, user=..., client=None
) -> list[Operation]Operations matching the filters, newest first (or the first limit).
status="live" means every non-terminal status; key is a key id or
prefix (nk_live_ab12); agent an Agent or id.
NoneNoneNoneNoneNoneNoneNoneNoneReturn at most this many.
None)Which users a read covers: noclick.ALL_USERS, noclick.NO_USER, a User, or your id for one. It filters without acting as that user.
None)Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)Every matching operation, fetched page by page.
See what failed in the last day:
from datetime import datetime, timedelta, timezone
yesterday = datetime.now(timezone.utc) - timedelta(days=1)
for op in noclick.Operation.list(status="failed", created_after=yesterday):
print(op.kind, op.error)Or the turns one agent is still working on:
support = noclick.Agent.from_name("support")
live = noclick.Operation.list(kind="agent.message", status="live", agent=support)A read acts for the current user; user_filter reads across them:
failed = noclick.Operation.list(status="failed", user_filter=noclick.ALL_USERS, limit=20)list_pageclassmethodasync
noclick.Operation.list_page(
*, cursor=None, limit=None, kind=None, status=None, status_not=None, agent=None,
key=None, created_after=None, created_before=None, updated_after=None,
user_filter=None, user=..., client=None
) -> PageOne page of matching operations; pass next_cursor back as cursor.
The next_cursor of the page before; omit it for the first page.
None)Return at most this many.
None)NoneNoneNoneNoneNoneNoneNoneNoneWhich users a read covers: noclick.ALL_USERS, noclick.NO_USER, a User, or your id for one. It filters without acting as that user.
None)Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)A page whose data holds the operations and whose next_cursor is None on the last page.
page = noclick.Operation.list_page(status="failed", limit=100)
while True:
for op in page.data:
print(op.id, op.kind, op.error)
if page.next_cursor is None:
break
page = noclick.Operation.list_page(status="failed", limit=100, cursor=page.next_cursor)summaryclassmethodasync
noclick.Operation.summary(
*, kind=None, status=None, status_not=None, agent=None, key=None,
created_after=None, created_before=None, updated_after=None, user_filter=None,
user=..., client=None
) -> OperationSummaryHow many operations match the same filters, by status.
NoneNoneNoneNoneNoneNoneNoneNoneWhich users a read covers: noclick.ALL_USERS, noclick.NO_USER, a User, or your id for one. It filters without acting as that user.
None)Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)The total, and in by_status a count for every status, zeros included.
counts = noclick.Operation.summary(kind="agent.message", status="live")
print(counts.total, counts.by_status["running"], counts.by_status["awaiting_human"])from_idclassmethod
noclick.Operation.from_id(operation_id, *, client=None, user=...) -> OperationA lazy reference to an operation by id.
The client to send with; defaults to the module client built from NOCLICK_API_KEY.
None)Who the call acts for. Defaults to the user set by noclick.as_user (none: the account itself).
An operation that loads the first time you read a field. Nothing is sent yet.
Operations outlive your process, so keep the id and pick it up anywhere:
op = noclick.Operation.from_id("3b9d6f2e-1c4a-4e8b-9f7d-2a6c5e8b1d40")
reply = op.wait()Or check on it without waiting:
op = noclick.Operation.from_id("3b9d6f2e-1c4a-4e8b-9f7d-2a6c5e8b1d40").hydrate()
print(op.status, op.done)waitasync
operation.wait(timeout=None) -> AnyBlock until the operation finishes.
Raises the operation's error if it failed, OperationCanceled /
OperationExpired, or OperationTimeout after timeout seconds.
NoneThe outcome, typed for the operation's kind: an AgentReply from agent.send,
True or False from human.approve, the connected Connection from a
connect link. A kind the client has no type for gives the raw result.
Start an agent turn, then block on it:
agent = noclick.Agent.from_name("support")
turn = agent.send("Draft the monthly report", user="user_42")
reply = turn.wait(timeout=120)
print(reply.text)A timeout leaves the operation running, so you can wait on it again later:
op = noclick.Operation.from_id("3b9d6f2e-1c4a-4e8b-9f7d-2a6c5e8b1d40")
try:
reply = op.wait(timeout=30)
except noclick.OperationTimeout:
print(op.status) # still runningtool_callsasync
operation.tool_calls() -> list[ToolCall]The tool calls an agent.message turn made, oldest first (none for other kinds).
Each call's tool, arguments, status, duration and a preview of its result.
agent = noclick.Agent.from_name("support")
turn = agent.send("Find last week's invoices", user="user_42")
turn.wait()
for call in turn.tool_calls():
print(call.tool_name, call.status, call.duration_ms)eventsiterator
operation.events(*, interval=1.0) -> Iterator[OperationEvent]Yield an OperationEvent for each status or step change until it finishes.
1.0The changes, polled every interval seconds: first the status (and step) it has
now, last its final one. An event's result is the raw result, not the typed outcome
wait() returns.
Print each change as it happens, a CLI agent's turn going from delivering to working:
op = noclick.Operation.from_id("3b9d6f2e-1c4a-4e8b-9f7d-2a6c5e8b1d40")
for event in op.events(interval=5):
print(event.status, event.step, event.updated_at)In async code, iterate the .aio() form:
op = noclick.Operation.from_id("3b9d6f2e-1c4a-4e8b-9f7d-2a6c5e8b1d40")
async for event in op.events.aio():
print(event.status)cancelasync
operation.cancel() -> OperationAsk the server to cancel.
This operation, now canceled.
Cancel a turn you no longer need:
agent = noclick.Agent.from_name("support")
turn = agent.send("Draft the monthly report", user="user_42")
turn.cancel()
print(turn.status) # canceledAn operation that already finished can't be canceled:
op = noclick.Operation.from_id("3b9d6f2e-1c4a-4e8b-9f7d-2a6c5e8b1d40")
try:
op.cancel()
except noclick.Conflict as err:
print(err.operation_status) # succeeded, failed, ...undoasync
operation.undo() -> dict[str, Any]Take back a reversible write, once: a deletion comes out of the trash, a dict's cleared keys come
back, a migrated database is restored to the moment before, a promote puts production back on the release
before with the app's databases as they were. What reached the world (an email sent, a payment) has no
undo: that raises noclick.Conflict, as does undoing twice.
What the undo put back, as its own API shows it (a restore of a database is an operation of its own).
item = noclick.Dict.from_name("rates-cache").delete()
noclick.Operation.from_id(item.operation_id).undo()OperationEvent
class OperationEventA status or step change observed by Operation.events(): step is where a running
operation is (delivering or working for a CLI agent's turn, submitted or scheduled
for a port; None otherwise).
| status | str | |
| step | str | None | |
| updated_at | Any | |
| result | Any | |
| error | Error | None | |
| raw | dict[str, Any] | The response JSON exactly as the API sent it. |
OperationSummary
class OperationSummaryHow many operations match, by status.
| total | int | |
| by_status | dict[str, int] | |
| raw | dict[str, Any] | The response JSON exactly as the API sent it. |
OperationTransition
class OperationTransitionOne status change the server recorded (by names who canceled).
| status | str | |
| at | Any | |
| by | str | None |