Guide
How to build an agent.
Define three things and let a runtime handle the rest: the objective the agent is trying to achieve, the inputs it can read, and the tools it can reach. Everything below uses the published @coresourceai/sdk package, on Node 20 or newer.
Step 1
Install the SDK
npm install @coresourceai/sdk
export CORESOURCE_API_KEY='your-api-key'Step 2
Define the objective
import { createAgent } from '@coresourceai/sdk';
const agent = createAgent({
type: 'saga',
model: 'deepseek/deepseek-v4-flash',
effort: 'xhigh',
systemPrompt: `You reconcile invoices against the ledger.
Follow the field rules exactly, and never invent a value
a source system did not state.`,
outputs: ['report.md']
});
const result = await agent.run('Reconcile last quarter and flag exceptions.');
console.log(result.text);The system prompt is where your judgement lives: the field rules, the quality bar, the things the agent must never do. It is the part worth iterating on, and the part that is genuinely yours.
The type decides how much machinery the run gets. A one-shot is a single response with a limited tool surface, an agent is an iterative tool loop, and a saga plans first and then executes durable multi-step work.
Step 3
Give it tools
Three ways to attach a tool, and they differ mainly in where the credential lives.
Your own functions
const agent = createAgent({
type: 'agent',
systemPrompt: 'Use the available business data.',
tools: [{
name: 'lookup_invoice',
description: 'Look up an invoice by ID.',
inputSchema: {
type: 'object',
properties: { id: { type: 'string' } },
required: ['id'],
additionalProperties: false
},
handler: async (input, { signal }) => {
signal.throwIfAborted();
return lookupInvoice(String(input.id), { signal });
}
}]
});A name, a description, an input schema and a handler. The handler runs in your process, so nothing about your credentials is passed to Coresource: the tool context carries only a call id, an abort signal and a deadline. Whatever client or key the handler closes over stays yours.
A hosted MCP server
import { CoreSource, createAgent } from '@coresourceai/sdk';
const coresource = new CoreSource();
// Saved once. CoreSource encrypts the credential and injects it only at its
// outbound relay: agent definitions, runs and events never contain it.
const research = await coresource.mcp.connect({
name: 'Research',
url: 'https://mcp.example.com/rpc',
auth: { type: 'bearer', token: process.env.RESEARCH_MCP_TOKEN! }
});
const agent = createAgent({
type: 'saga',
systemPrompt: 'Prefer primary sources and retain direct URLs.',
mcpServers: [{ connectionId: research.id, alias: 'research' }]
});For a remote server, save the credential once and reference the connection by id. The agent definition never contains a URL or a token, and neither do runs or events. Static credentials are encrypted and write-only, rotated with rotateCredential().
For providers with OAuth you do not handle a token at all. Open the returned URL once and the connection reports itself connected.
const github = await coresource.mcp.connect({
name: 'GitHub',
url: 'https://api.githubcopilot.com/mcp/',
auth: { type: 'oauth', provider: 'github' }
});
// Send the user here once. After the callback the connection reports
// "connected" and github.id can go straight into mcpServers.
console.log(github.authorizationUrl);Connections made with an API key are account-scoped; connections made from a signed-in user are private to that user unless an organization owner widens them. To run the MCP client in your own process instead, call agent.registerMcpServer() before the first run.
The runtime catalog
const codingAgent = createAgent({ type: 'agent' });
// file_read, file_write, file_edit, glob, grep, web_fetch, execute
codingAgent.registerRuntimeTools();Filesystem and command tools are not registered by default, because they act on whatever machine the agent is running on. Add them when an agent genuinely needs them and keep the surface narrow otherwise: an agent cannot misuse a tool it was never given.
Whichever route you take, adding a second system should be a second tool config rather than a second parser. If connecting a vendor means writing translation code, the integration surface grows with every vendor you support.
Step 4
Take the output
const report = result.artifacts.find((a) => a.name === 'report.md');
if (report) console.log(await report.text());
await agent.close();Declare the artifacts a run should produce and they are persisted with it, alongside the record of how the run reached them.
The point
What you did not have to write
There is no queue, no state store, no retry logic and no orchestration graph above. Decomposition, recovery and the record of what happened belong to the runtime.
What is left in your repository is a description of the objective and the tools. Small enough to read in one sitting, and small enough to change when the requirements do.
Common questions
How do you build an AI agent?
Define three things and let a runtime handle the rest: the objective the agent is trying to achieve, the inputs it can read, and the tools it can reach. With the Coresource SDK that is a single createAgent call, then agent.run().
What is the difference between a prompt and an agent?
A prompt produces one response. An agent has an objective, a set of tools it can call, and a loop that continues until the objective is met or it fails, which is why it needs durable state and a record of what it did.
Do you need to write orchestration code to build an agent?
No. Decomposition, retries, recovery and state are the runtime's job. Application code supplies the agent definition and the prompt; adding a second data source is another tool config, not another parser.