Embedding
The CLI is also a library that can be embedded in a host process. There, it runs the same command classes, supplying credentials, identity, and policy per invocation, with no new process spawned between calls.
Why the runtime exists
Typical CLIs were designed for one person running one command: one process, arguments from the shell, output to the terminal, exit when done. An agent runs the same commands repeatedly, often inside a process serving many agents at once, with identity and credentials arriving per request. A process per command means a login per command.
The alternative is a CLI that is also a library. With aclif, every command is a class. Each execution is an invocation that supplies the credentials, identity, and policy for that one call. Inside a host process, the process stays up, so the login and the connection made on the first call are reused by every call after it.
Three ways to run it
1. Run by the agent
The agent process spawns the binary, executes the command, and reads the JSON it returns. Credentials come from flags, environment variables, or a profile in the agent's own environment. Policy comes from the same profile file, and the audit line writes to stderr. Use this when one agent, one operator, and one set of credentials share a trust boundary.
2. Run by a host application, the design-time case
An application sits between the model and aclif and holds the credentials. The model calls a tool the application defines, and the application executes the command, in-process or by passing a command string to the CLI. A person or an authoring tool uses this to let a model discover providers, introspect commands, and validate the exact command it will write into an agent. One tool describing the grammar replaces one tool per operation. Use this when the model must never hold credentials and tool definitions must stay out of its context.
3. Run by a gateway, the runtime case
A deployed agent submits commands, and one long-lived process serves many such agents. The gateway resolves credentials from the enterprise vault per request, checks policy against the acting user, records every call, and keeps connections warm. The agents hold no provider credentials and cannot widen their own scope. Use this when many agents share providers and one place must hold policy and audit.
Each request becomes an Invocation that
holds the user's identity and forwarded SSO claims, a
credential resolver over the host's secrets store, a
capability check that runs on the command's declared
metadata before its code loads, a rate-limit hook, a
reporter, and the shared connection pool keyed by provider,
instance, identity, and auth type.
A gateway deployment requires both an identity provider and a secrets vault. Each request includes the user's SSO token. The gateway verifies it and attaches the identity to the invocation, where every command, hook, and audit event can read it. Credentials come from the vault through a resolver the gateway author must provide. The framework calls it before each command and hands the secret to the provider client and nowhere else. When the resolver returns the acting user's own credential, the target system applies that user's permissions to the call, so the framework holds no second permission model that could drift from the first.
Three properties follow for any gateway built this way, independant of model behavior: the agent holds no credential, the agent cannot widen its scope, and every call is attributed to a person because the identity travels with the invocation.
Minimal host
The samples import from aclif. A CLI
scaffolded from aclif installs @aclif/core
under that alias. In a project of your own, install it the
same way:
npm install aclif@npm:@aclif/core
import {Runtime, EventReporter, StaticCredentialResolver} from 'aclif'
const runtime = await Runtime.start({cliRoot: '/path/to/your/cli'})
const reporter = new EventReporter()
const result = await runtime.run({
argv: ['salesforce', 'data', 'query', '--query', 'SELECT Id FROM Account LIMIT 1'],
context: {requestId: 'req-1', user: {id: 'u1', username: 'alice'}},
credentials: new StaticCredentialResolver(new Map([
['salesforce', {instanceUrl, accessToken, authType: 'session'}],
])),
pool: runtime.pool,
reporter,
hooks: {
capabilityGate: async ({aciMetadata}, ctx) =>
aciMetadata.mutability === 'delete' && !ctx.user
? {allowed: false, error: {code: 'DENIED', message: 'Deletes need a user'}}
: {allowed: true},
},
})
console.log(result.exitCode, reporter.envelope())
cliRoot is the package root of the CLI whose
commands the runtime should load: the framework itself for
the reference binary, or your own package for a
CLI built on aclif.
What the host supplies
| On the invocation | Purpose |
|---|---|
context | the per-request record: request id, the acting user, forwarded SSO claims, an abort signal, audit metadata such as the calling agent |
credentials | a
CredentialResolver. Static,
Env, Profile, and
Chain resolvers are built in. A vault-backed
resolver is not included; the host writes one against the
one-method interface and resolves per request |
pool | the runtime's connection pool. Two instances never share a connection |
reporter | collects the envelope
and events. EventReporter is the reference
implementation |
hooks.capabilityGate | allow or refuse from the command's declared metadata before its code loads |
hooks.rateLimit | called per provider and operation before execution |
tenantCache, aliasStore | optional:
the host's own store for tenant catalogs and alias sets, so
learn, --schema, and
--canonical read the host's data |
Health monitoring, probe timers, and declarative manifests that turn one HTTP endpoint into a command are part of the runtime as well. The reference is EMBEDDING.md.