Agent Folders
Keep many agents in one repo, each a self-contained folder, with a CLI to scaffold and test them.
Little Harness: published on npm as little-harness@0.2.0-alpha.0 (2026-10-05). Sections labeled UNRELEASED describe source-only changes. Release status
A workspace lets you keep many harnesses in one repo, each as its own folder under agents/. A
folder is loaded into a runnable harness by auto-discovering its tools/, skills/, and workflows/ — createHarness
itself is unchanged, and inline createHarness(...) still works exactly as before.
Layout
The workspace marker little-harness.json ({ "agents": "agents" }) sits at the root; each agent is a
folder under agents/, named by the agent's id:
agent.ts
The entry is a plain createHarness call. Tools, skills, and workflows are omitted here — they are
discovered from the folder and merged in when the agent is loaded.
import { createHarness, localHost } from "@little-stack/harness";
import { anthropic } from "@ai-sdk/anthropic";
export default createHarness({
model: anthropic("claude-opus-4-6"), // set the selected provider's required env vars
system: "You are a support agent.", // or omit and use instructions.md
host: localHost(),
});When generated by little-harness init, the provider import and model id come from the AI SDK
provider/model selected in the wizard — the anthropic provider is the default (openai in the
unreleased source). The wizard currently offers 24 provider choices: the official AI
SDK language-model providers plus a custom OpenAI-compatible API scaffold. You can also type a custom
provider model id when the exact model is not listed.
If both agent.ts's system and a sibling instructions.md exist, agent.ts wins; if system is
omitted, instructions.md becomes the system prompt.
Tools — tools/*.ts
Each file default-exports a normal AI SDK tool; the tool name is the filename without extension. Inline
tools passed to createHarness win on a name collision.
// tools/lookup_order.ts
import { tool } from "ai";
import { z } from "zod";
export default tool({
description: "Look up an order by id.",
inputSchema: z.object({ orderId: z.string() }),
execute: async ({ orderId }) => ({ orderId, status: "shipped" }),
});Skills — skills/<name>/
Each subdirectory is one skill:
SKILL.mdonly → a local skill (skill("./skills/<name>")).skill.ts(default-exportsskill(...)) → a declared skill — used directly. This is how you pull in remote/GitHub skills, and it wins over a siblingSKILL.md.
// skills/frontend-design/skill.ts
import { skill } from "@little-stack/harness";
export default skill("https://github.com/anthropics/skills#v1.2.0", { skills: ["frontend-design"] });Remote skills resolve, pin (by commit), and stage exactly as when passed inline to createHarness — at
runtime local and remote skills are both staged under .agents/skills/<name>/.
Workflows — workflows/*.ts
Each file default-exports a HarnessWorkflow — typically a Little Workflow adapted with
asHarnessWorkflow — and becomes one tool named from the workflow id. Discovered workflows are appended
to any workflows passed in agent.ts. Test and spec files are skipped.
// workflows/refund.ts
import { asHarnessWorkflow, defineWorkflow } from "@little-stack/workflow";
const refund = defineWorkflow({ id: "billing.refund", /* input, output, model, tools */ });
export default asHarnessWorkflow(refund, { definitionIdentity: "billing.refund@1" });A bare defineWorkflow(...) default export is rejected at load time: it has no executionMode or
definitionIdentity. See Triggering a workflow from an agent
for the tool result shape and workflowBudgets.
Schedules — schedules/*.ts
Each file default-exports defineSchedule({ cron, prompt }), a run of the agent
on a timer; the file name is its id. loadHarness() ignores them;
little-harness dev and start run them. See
Serving An Agent.
// schedules/morning-backlog.ts
import { defineSchedule } from "@little-stack/harness";
export default defineSchedule({
cron: "0 9 * * 1-5",
timezone: "Asia/Hong_Kong",
prompt: "Pre-approve every application received since yesterday.",
});Connectors And UI
connectors/ is reserved for connection layers such as Slack, Telegram, or web chat routes. It is
ignored by loadHarness(). little-harness start serves each one at /connectors/<id>; in your
own server, use loadChatSdkConnector() or loadWebRichConnector() from
@little-stack/harness/connectors/runtime when a route wants to expose an agent through a chat surface.
String connector ids still work from the runtime subpath; the loaders import discovery dynamically
when needed. Use @little-stack/harness/connectors when the route directly calls discovery helpers such as
discoverConnectors().
Simple connectors can be flat files like connectors/slack.ts. Use nested connector folders when the
connector owns extra files:
connectors/
slack/
connector.ts
tools/
add_reaction.ts
renderers/
lookup_order.tsxConnector tool extensions are scoped by folder, so connectors/slack/tools/add_reaction.ts extends
the add_reaction tool only when the slack connector is active. The extension file does not repeat
connector: "slack"; the folder name is the connector id. These tools are for real platform
capabilities such as reactions or modals. Presentation-only web/json-render code belongs in ui/ or
connector renderers, not in model-visible tools.
ui/ is an optional home for shared web UI types and components. It is useful when multiple web apps
render the same agent-specific tool output parts.
CLI
npx @little-stack/harness@alpha init # guided workspace wizard
little-harness init --here # scaffold into the current directory
little-harness new support # add agents/support/ to an existing workspace
little-harness test support # load the agent and chat with it interactively (REPL)
little-harness dev support # serve it, restart on change, chat in the terminal
little-harness start support # serve it: connectors, an HTTP API, schedulestest opens an interactive REPL: type a message, the agent streams its reply, and the conversation
persists across turns. Assistant text, reasoning, tool calls, and tool results are printed as labeled
blocks, with tool input and output payloads shown when available. Type :reset to clear the
conversation or :exit to quit.
Loading from code
You can load an agent folder yourself (the CLI uses these under the hood):
import { loadHarness, loadWorkspace } from "@little-stack/harness";
const { agents } = await loadWorkspace(); // enumerate agents/*
const harness = await loadHarness("agents/support"); // a runnable Harness with tools, skills, and workflows mergedTo run a folder's agent on a different host than the one its agent.ts configures — for example a
throwaway data directory in a test or a script — pass host. The loaded harness then takes its
sessions from that host:
import { loadHarness, localHost } from "@little-stack/harness";
const harness = await loadHarness("agents/support", {
host: localHost({ dataDir: ".little-harness-test" }),
});UNRELEASED: the
hostoption exists only in the current source. In published0.2.0-alpha.0,loadHarness(agentDir)takes no options.