Human input
When an agent calls the hosted ask_user tool, the run pauses durably with
status awaiting_input. It is not blocked on an open connection and it is not
burning model tokens while it waits. The pause is a checkpoint, and it can
outlive your process.
This is what makes long-running agents workable in real applications: the agent can ask for a decision at hour three without you having to hold a socket open for three hours.
Two ways to handle it#
| Approach | Use when |
|---|---|
run() with onInputRequired | The answer is available in-process, a request handler, a CLI prompt |
start() + resume() | The answer arrives later while the process holding the handle is active |
HTTP /runs/{id}/resume | A different process or service answers by run id |
The callback#
For request/response applications, one callback handles every pause.
import { createAgent } from '@coresourceai/sdk';
const agent = createAgent({
type: 'saga',
systemPrompt: 'Ask before making irreversible choices.'
});
const result = await agent.run('Prepare the launch plan.', {
onInputRequired: async ({ question }) => {
return askYourUser(question);
}
});
console.log(result.status, result.text);
await agent.close();
declare function askYourUser(question: string): Promise<string>;The callback may be invoked more than once (a run can pause repeatedly) and each answer resumes the same durable run from the same state.
Without onInputRequired, run() does not wait indefinitely. It returns the paused result, and it is your job to notice:
import { createAgent } from '@coresourceai/sdk';
const agent = createAgent({ type: 'agent' });
const result = await agent.run('Pick a launch date and proceed.');
if (result.status === 'awaiting_input') {
console.log('The run is waiting:', result.requiredAction?.question);
}
await agent.close();Checking status before using text matters here specifically. A paused run's text is not the answer to your prompt.
Resuming manually#
When the answer comes from elsewhere (a Slack message, a support queue, a person who is not online yet) use start() and drive the loop yourself.
import { createAgent } from '@coresourceai/sdk';
const agent = createAgent({ type: 'saga' });
const run = await agent.start('Prepare the launch plan and ask before choosing a date.');
let result = await run.result();
while (result.status === 'awaiting_input') {
const action = result.requiredAction;
if (!action) {
throw new Error('The run paused without a question.');
}
console.log(action.question);
const answer = await collectAnswer(action.question);
await run.resume(answer);
result = await run.result();
}
console.log(result.status, result.text);
await agent.close();
declare function collectAnswer(question: string): Promise<string>;result() returns as soon as the run pauses, so this loop does not spin. Each resume() continues the run from its checkpoint.
The required action#
import type { AgentRequiredAction } from '@coresourceai/sdk';
function describe(action: AgentRequiredAction) {
return {
type: action.type, // always 'provide_input'
question: action.question,
callId: action.callId, // correlates the answer with the tool call
schema: action.inputSchema // JSON Schema, when the answer is structured
};
}inputSchema is worth handling. When present, the agent is not asking for prose. It wants a structured value, and resume() accepts any JSON value, not only a string:
import { createAgent } from '@coresourceai/sdk';
const agent = createAgent({ type: 'saga' });
const run = await agent.start('Plan the migration; confirm the window with me.');
const result = await run.result();
if (result.status === 'awaiting_input' && result.requiredAction?.inputSchema) {
// The agent asked for a shape, not a sentence.
await run.resume({ start: '2026-08-03T22:00:00Z', durationHours: 4 });
}
await agent.close();Across processes#
Because the pause is durable, the process that resumes a run does not have to be the process that started it. Persist the run id before the first process exits:
import { createAgent } from '@coresourceai/sdk';
// Process A: start the work and hand the run id to your queue.
const agent = createAgent({ type: 'agent' });
const run = await agent.start('Audit the vendor contracts and ask before filing the report.');
await saveRunId(run.id);
await agent.close();
declare function saveRunId(id: string): Promise<void>;A later process addresses the run through HTTP:
import process from 'node:process';
// Process B: answer the paused run by its id.
const runId = await loadRunId();
const answer = await waitForHumanReply(runId);
const response = await fetch(
`https://api.coresource.ai/v1/runs/${encodeURIComponent(runId)}/resume`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CORESOURCE_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ input: answer })
}
);
if (!response.ok) {
throw new Error(`Resume failed with HTTP ${response.status}`);
}
declare function loadRunId(): Promise<string>;
declare function waitForHumanReply(runId: string): Promise<string>;Continue polling or streaming that same run id. See HTTP API.
Design guidance#
Ask for decisions, not for data. If the agent can look something up with a tool, give it the tool. Pauses are for judgement calls and approvals.
Make questions self-contained. The person answering may see the question in a Slack notification with none of the run's context.
Expect repeats. Write the handler as a loop or a callback, never as a single if.
Set a policy for stale pauses. Decide when your application should cancel a run that remains in awaiting_input. Do not rely on the waiting client process to stay alive.
