@nexrall/agent
v0.1.0
Published
Ergonomic agent runtime for Nexrall Code — createAgent()/registerSkills()/connectMCP()/delegate()/spawn()/observe()/resume(), built on @nexrall/code-core.
Maintainers
Readme
@nexrall/agent
An ergonomic agent runtime built on top of @nexrall/code-core —
the same agent loop that powers Nexrall Code (VS Code) and the Nexrall CLI.
@nexrall/code-core is deliberately low-level: runAgentLoop() is a single
stateless call that takes a full options object every time, and MCP/
checkpoints/sub-agents are separate classes you wire up by hand. That's the
right shape for a client that already owns its own UI event loop. It's the
wrong shape for "give me a working agent in a few minutes." @nexrall/agent
is that layer — a stateful Agent object with the API shape developers
expect from modern agent SDKs (Claude Agent SDK, OpenAI's Agents SDK):
npm install @nexrall/agentimport { createAgent } from '@nexrall/agent';
const agent = createAgent({ workDir: process.cwd(), model: 'claude-sonnet-5' });
agent.observe({
onText: (t) => process.stdout.write(t),
onToolUse: (name) => console.error(`[tool] ${name}`),
});
const result = await agent.run('Refactor the auth module to use async/await.');
console.log(result.text);What each method does — and what it's built on
Nothing here reimplements agent-loop logic. Every method is a thin wrapper
over a real @nexrall/code-core primitive, so behavior (permissions, budget
limits, checkpoints) stays identical to the CLI/VS Code extension.
| Method | Built on |
|---|---|
| createAgent(opts) / agent.run(prompt) | runAgentLoop() — holds conversation state between calls, which the raw function does not |
| agent.registerAgentType(name, def) | loadAgentTypesWithWarnings()'s extra parameter — define a sub-agent as a plain object instead of a .nexrall/agents/*.md file |
| agent.registerSkills(skills) | loadSkillsWithWarnings()'s extra parameter — bundle a skill inside your own npm package instead of shipping a SKILL.md |
| agent.connectMCP(config) | McpManager — pass a workDir (reuses ~/.nexrall/mcp.json / <workDir>/.nexrall/mcp.json) or an inline server map |
| agent.delegate(req) / agent.spawn(req) | dispatchSubAgent() — the exact function the task tool itself uses (depth limit, per-depth concurrency, session budget) |
| agent.resume(id, prompt) | @nexrall/code-core's in-memory agent registry — see Sessions below for what this does and doesn't guarantee |
| agent.observe(callbacks) | Fans a single set of AgentLoopOptions callbacks out to every registered observer — attach/detach at any time |
| agent.rewind(turnId?) | CheckpointManager — roll back file edits + conversation to an earlier point |
| agent.connectA2A(config) | Not implemented yet — see below |
Sessions — an honest limitation, not a gap
resume() and the ids delegate()/spawn() return are backed by
@nexrall/code-core's agent registry, which is deliberately in-memory
only — never persisted to disk. A resumable id is only resumable within the
lifetime of the process that created it, and the registry is bounded (oldest
entries evicted first once too many accumulate).
This is a real, intentional design decision in code-core: a sub-agent's
transcript is unredacted tool output (file contents, command output —
whatever a repo happens to contain), and persisting it would create a new
durable copy of material nobody asked to be stored. If your application needs
resumability across restarts, persist agent.messages yourself and pass it
back via createAgent() + replaying — this package does not make that
decision for you.
connectA2A() — reserved, not implemented
NAP (the Nexrall Agent Protocol — agent-to-agent identity, task delegation,
and trust across organizational boundaries) is currently a design
document, not shipped code. See docs/NAP_AGENT_TO_AGENT_PROTOCOL.md in the
main Nexrall repository for its current status and compliance levels.
connectA2A() exists in this package's type signature today and throws a
clear, actionable error explaining why — reserving the name and shape so a
caller gets told the real reason now, instead of a generic "not a function"
once NAP ships. It will be implemented once NAP reaches at least L1 (task
lifecycle over A2A's own REST binding).
Relationship to @nexrall/code-core
This package has no agent-loop logic of its own. Every method delegates
to a real, tested @nexrall/code-core function:
- Programmatic agent types/skills merge through the exact same precedence
rule as file-based ones (
extrawins on a name collision, same as a more specific source already beats a more general one). delegate()/spawn()drivedispatchSubAgent()— the same functionrunAgentLoop's owntasktool handling calls internally, so there is one dispatch path, not two that could drift apart.resume()re-derives the sub-agent's authorized name from storage exactly like a model-drivenresume_agent_idcall would, so a permissiondenyrule added after a sub-agent ran still applies to resuming it.
If you need lower-level control (custom tool executors, VS Code semantic
tools, raw SSE handling), use @nexrall/code-core directly — this package is
an ergonomic layer on top, not a replacement.
