Documentation Index
Fetch the complete documentation index at: https://mintlify.com/Effectful-Tech/clanka/llms.txt
Use this file to discover all available pages before exploring further.
AgentExecutor is the layer that sits between the Agent and the outside world. When the language model decides to act, it emits JavaScript. AgentExecutor compiles that JavaScript, runs it inside a Node.js vm sandbox, and streams back console.log output so the model can observe the results.
How the sandbox works
Each script the model generates is wrapped in an async function main() and executed in a fresh vm context via NodeVm.Script.runInNewContext. The sandbox has no access to Node.js built-ins (require, import, process, etc.). Instead, every built-in tool handler is injected as a named global function. When the model calls readFile(...), it is calling the actual Effect handler through a promise-backed bridge.
Tool calls are tracked with a running counter. The executor waits until all in-flight promises have settled before flushing final output.
The AgentExecutor interface
class AgentExecutor extends Context.Service<AgentExecutor, {
readonly capabilities: Effect<Capabilities>
execute(options: {
readonly script: string
readonly onTaskComplete: (summary: string) => Effect<void>
readonly onSubagent: (message: string) => Effect<string>
}): Stream<string>
executeUnsafe<Tool extends keyof typeof AgentTools.tools>(options: {
readonly tool: Tool
readonly params: (typeof AgentTools.tools)[Tool]["parametersSchema"]["Encoded"]
}): Effect<Schema.Json>
}>()("clanka/AgentExecutor") {}
| Method | Description |
|---|
capabilities | Returns a Capabilities object describing what the executor can do |
execute | Run a model-generated script and stream stdout lines |
executeUnsafe | Call a single named tool directly, bypassing the sandbox |
Capabilities
When the Agent starts a turn it fetches the executor’s Capabilities to build the system prompt:
class Capabilities extends Schema.Class<Capabilities>("Capabilities")({
toolsDts: Schema.String, // TypeScript declarations for all tool globals
agentsMd: Schema.Option(Schema.String), // contents of AGENTS.md if present
supportsSearch: Schema.Boolean, // true when SemanticSearch layer is provided
}) {}
toolsDts is generated from the registered toolkit and is injected verbatim into the system prompt so the model knows which globals are available and what their signatures are.
agentsMd is read from AGENTS.md in the working directory on every turn. If the file exists, its contents are wrapped in a clearly-labelled block and prepended to the system prompt:
# AGENTS.md
The following instructions are from ./AGENTS.md in the current directory.
You do not need to read this file again.
**ALWAYS follow these instructions when completing tasks**:
<!-- AGENTS.md start -->
… contents of AGENTS.md …
<!-- AGENTS.md end -->
This is the conventional way to give project-specific standing instructions to the agent without modifying your application code.
Deployment layers
layerLocal — in-process sandbox
Runs the executor in the same process. The tools operate directly on the local filesystem and can spawn child processes.import { AgentExecutor } from "clanka"
import { NodeServices, NodeHttpClient } from "@effect/platform-node"
import { Layer } from "effect"
const ExecutorLayer = AgentExecutor.layerLocal({
directory: process.cwd(),
}).pipe(
Layer.provide(NodeServices.layer),
Layer.provide(NodeHttpClient.layerUndici),
)
Pass a custom Toolkit to the tools option to add your own tool handlers alongside the built-ins. layerRpcServer — serve the executor over RPC
Exposes the executor’s RPC group so a remote client can connect. Use this when you want to isolate execution in a separate process or container.import { AgentExecutor } from "clanka"
import { NodeServices, NodeHttpClient } from "@effect/platform-node"
import { Layer } from "effect"
const ServerLayer = AgentExecutor.layerRpcServer({
directory: process.cwd(),
}).pipe(
Layer.provide(NodeServices.layer),
Layer.provide(NodeHttpClient.layerUndici),
Layer.provide(RpcServerProtocol), // your transport layer
)
layerRpc — connect to a remote executor
Creates an AgentExecutor that forwards all calls to a running layerRpcServer via an RpcClient.Protocol.import { AgentExecutor } from "clanka"
import { Layer } from "effect"
const ExecutorLayer = AgentExecutor.layerRpc.pipe(
Layer.provide(RpcClientProtocol), // your transport layer
)
executeUnsafe lets you invoke any registered tool handler outside of a running agent turn. This is useful for testing, migrations, or one-off scripts.
import { AgentExecutor } from "clanka"
import { Effect } from "effect"
Effect.gen(function* () {
const executor = yield* AgentExecutor.AgentExecutor
const result = yield* executor.executeUnsafe({
tool: "readFile",
params: { path: "package.json", startLine: 1, endLine: 5 },
})
console.log(result) // Schema.Json — the raw encoded output
}).pipe(Effect.provide(ExecutorLayer))
executeUnsafe bypasses the vm sandbox entirely and runs the handler directly in the current process context. Do not use it with untrusted input.
Pass a Toolkit to layerLocal or layerRpcServer to extend the set of globals available in the sandbox. Custom tool handlers must not conflict with the built-in tool names.
import { AgentExecutor } from "clanka"
import { Toolkit, Tool } from "effect/unstable/ai"
import { Schema, Layer } from "effect"
const MyTools = Toolkit.make(
Tool.make("getWeather", {
description: "Fetch current weather for a city",
parameters: Schema.Struct({ city: Schema.String }),
success: Schema.String,
}),
)
const MyToolHandlers = MyTools.toLayer({
getWeather: Effect.fn(function* ({ city }) {
return `Sunny, 22°C in ${city}`
}),
})
const ExecutorLayer = AgentExecutor.layerLocal({
directory: process.cwd(),
tools: MyTools,
}).pipe(
Layer.provide(NodeServices.layer),
Layer.provide(NodeHttpClient.layerUndici),
Layer.provide(MyToolHandlers),
)