Inputs
Read text, structured payloads, and input sources with useInput
useInput() reads the input that caused the current agent render. Use it to
include the current request in the instructions or change behavior based on
where the request came from.
import { useInput } from "@opencomputer/agent";
export default function Agent() {
const input = useInput();
return input.text
? `Help the user with this request: ${input.text}`
: "Help the user with their request.";
}useCurrentInput() is an alias for useInput().
Input shape
interface AgentInput {
readonly source: InputSource;
readonly text?: string;
readonly payload?: DataValue;
readonly answer?: QuestionAnswer;
readonly steering?: readonly HeldInput[];
}| Field | Meaning |
|---|---|
source | How the work entered the session: user, channel, schedule, webhook, subagent, system or event |
text | Optional human-readable message |
payload | Optional structured JSON value supplied by the source |
answer | Present only when this input answers the question an earlier turn asked; see Answers and steering |
steering | Inputs written while that question was open that did not answer it, in order |
DataValue means any JSON-compatible value: null, a boolean, number,
string, array, or object. Both text and payload are optional, so check them
before use.
A payload comes from whatever started the turn. An application sends one on
the turns route next to the text, and the agent reads it
with source: "user"; a webhook delivers its body as the
payload; a schedule dispatches the payload it was
configured with. The agent reads all of them the same way.
A message from a connected Slack app arrives with
source: "channel" and a channel object naming the provider (slack),
the connectionId, and the Slack workspaceId, conversationId and userId
when Slack supplies them. An agent serving one workspace can ignore all of
this; an agent behind a shared Slack app uses it to decide whose records to
read.
A message from a Linear connection also arrives with
source: "channel", with channel.provider set to linear,
channel.workspaceId the Linear workspace, channel.conversationId the
Linear agent session and channel.userId the person who wrote the message
(on the first message, the session’s creator; absent when automation started
it).
Its payload is a LinearInputPayload: the issue, the session, the thread’s
comments, your workspace’s guidance for agents, and origin, which says
whether the issue had a parent and whether this agent’s own app created it.
origin is read when the session starts and does not change afterwards. The
fields are listed on Linear connections.
import { useInput, type LinearInputPayload } from "@opencomputer/agent";
export default function Agent() {
const input = useInput();
if (input.source === "channel" && input.channel.provider === "linear") {
const linear = input.payload as unknown as LinearInputPayload;
return linear.origin.createdByAgent && linear.origin.parent
? `Implement ${linear.issue.identifier}, part of ${linear.origin.parent.identifier}.`
: `Investigate ${linear.issue.identifier} and propose an approach.`;
}
return `Help with: ${input.text ?? "the current request"}`;
}Adapt to the source
The current direct and delegated flows can share one agent definition:
import { useInput } from "@opencomputer/agent";
export default function Agent() {
const input = useInput();
return input.source === "subagent"
? "Complete the delegated task and return a concise result."
: `Help with: ${input.text ?? "the current request"}`;
}Read structured payloads
Narrow or validate a payload before reading application-specific fields:
import { useInput } from "@opencomputer/agent";
type ResearchRequest = {
topic?: string;
depth?: "brief" | "deep";
};
export default function Agent() {
const input = useInput();
const request = (input.payload ?? {}) as ResearchRequest;
if (request.topic) {
return `Research ${request.topic} at ${request.depth ?? "brief"} depth.`;
}
return "Ask for a research topic before starting.";
}Validate untrusted payloads in a tool before performing side effects.
Event input
When an event subscription delivers
another session’s turn outcome to this one, the turn’s input has
source: "event" and an event:
import { useInput } from "@opencomputer/agent";
export default function Agent() {
const input = useInput();
if (input.source === "event") {
const { event } = input;
if (event.type === "turn.completed") {
return `Worker ${event.agentId} finished turn ${event.turnId}. Its final message: ${event.result?.text ?? "(none)"}. Decide what happens next.`;
}
return `Worker ${event.agentId}'s turn ${event.type === "turn.failed" ? "failed" : "was cancelled"} (${event.reason ?? "no reason"}). ${event.error ?? ""}`;
}
return `Help with: ${input.text ?? "the current request"}`;
}| Field | Meaning |
|---|---|
event.id | The source session’s event id for its terminal turn.* event |
event.type | turn.completed, turn.failed or turn.cancelled |
event.sessionId, event.turnId, event.agentId | Whose outcome this is |
event.occurredAt | When the source turn settled |
event.reason | Why it failed or was cancelled, when recorded |
event.error | The failure message, bounded, when it failed |
event.result | { text, truncated }: the final assistant message of a completed turn, bounded to 16 KB |
An event input carries no text; read event. The turn’s recorded input in
the event log is a text description of the same outcome.
The platform attests where the input came from through source; the
included message is another agent’s output and is data to reason about, not
instructions to follow.
Answers and steering
A turn can end with a question: the agent selects the
ask tool and the model calls it. The
reply is the next input, from any source, with answer set:
| Field | Meaning |
|---|---|
answer.questionId | The question this input answers |
answer.text | What the person wrote or selected |
answer.value | The chosen option’s value, when the text matched an option by value or label |
Input that arrives while the question is open without answering it does not
run a turn of its own. It is held and delivered with the answer as
steering, in the order it arrived; past a bounded number of items or 64 KiB,
the rest run as ordinary turns after the answer. In Linear, steering is
usually empty; see Ask before acting.
| Field | Meaning |
|---|---|
text, payload, channel | The held input, as it would have arrived on its own |
receivedAt | When OpenComputer received it |
providerTime | When the provider says it was written, when it says |
Read steering before acting on the answer. A constraint written while the
question was open can change what a “go” means, and the agent should revise
its proposal rather than act on an answer the steering has overtaken.
import { useInput, useTool } from "@opencomputer/agent";
export default function Agent() {
const input = useInput();
if (input.answer) {
const held = (input.steering ?? []).map((item) => `- ${item.text ?? ""}`).join("\n");
return `The person answered: ${input.answer.value ?? input.answer.text}.${
held ? `\nWritten meanwhile, read before acting:\n${held}` : ""
}`;
}
useTool("ask");
return "Read the request, propose a plan, and ask before making changes.";
}answer and steering sit beside payload, not inside it, so they read the
same from every source and never collide with an application’s payload.
Input is not a prompt UI
useInput() only reads admitted input. It does not itself pause the session
to ask a person a question; a turn asks with the ask
tool, and the answer arrives as a later input. The durable conversation and
later messages are managed by the session.
Input payload also differs from session data: a payload belongs to the current input, while session data can persist across renders.