Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
3f5b405
feat: daemon SDK MVP — connectDaemon, DaemonConnection, DaemonSession
varin-nair-factory May 22, 2026
4de6715
fix: daemon protocol compatibility — method prefix remapping, session…
varin-nair-factory May 23, 2026
8b8d795
chore: update daemon exports, protocol version, and test fixtures
varin-nair-factory May 23, 2026
b094c05
refactor: replace methodPrefix hack with standalone DaemonClient
varin-nair-factory May 26, 2026
942acba
fix: remap daemon notification method prefix for stream compatibility
varin-nair-factory May 26, 2026
7ffec3c
feat: add local daemon spawn, credential resolution, docs, and enum f…
varin-nair-factory May 26, 2026
792e9ff
fix: daemon discovery — probe well-known ports, cache target, dedupli…
varin-nair-factory May 26, 2026
3313603
fix: resolve lint, typecheck, and formatting issues in daemon test files
varin-nair-factory May 27, 2026
6d1f64b
fix: resolve FACTORY_DROID_BINARY via PATH and detach spawned daemon
varin-nair-factory May 27, 2026
0bfde5f
chore: remove redundant ad-hoc MCP test scripts
varin-nair-factory May 27, 2026
a409122
chore: clean up junk files, add stress test suite and daemon example
varin-nair-factory May 27, 2026
0cf9b46
fix: resolve lint, typecheck, and formatting issues in stress test suite
varin-nair-factory May 27, 2026
0f4d402
docs: cross-link exec and daemon usage guides
varin-nair-factory May 27, 2026
ff8ac0d
fix: plug MCP server leak, forward sessionSource, remove dead title f…
varin-nair-factory May 27, 2026
ed90bd7
feat!: require explicit apiKey for all SDK entry points
varin-nair-factory May 28, 2026
df0202c
fix: send apiKey as 'token' on daemon session init/load wire protocol
varin-nair-factory May 28, 2026
eb99f66
docs: fix code snippet accuracy in usage guides
varin-nair-factory May 28, 2026
555a588
refactor: address PR review feedback — remove dev mode, scope SDK fil…
varin-nair-factory May 28, 2026
b8cf864
refactor: extract shared streamFromClient() to deduplicate stream()
varin-nair-factory May 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
544 changes: 544 additions & 0 deletions docs/daemon-usage-guide.md

Large diffs are not rendered by default.

136 changes: 103 additions & 33 deletions docs/sdk-usage-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
npm install @factory/droid-sdk
```

Requires Node.js 18+ and the `droid` CLI on your PATH.
Requires Node.js 18+ and the `droid` CLI on your PATH. This guide covers **exec mode** (`run()`, `createSession()`), which spawns a subprocess per session. For WebSocket-based daemon mode with concurrent sessions, see the [Daemon Usage Guide](./daemon-usage-guide.md).

```ts
import { run } from '@factory/droid-sdk';

const result = await run('What files are in this directory?', {
apiKey: process.env.FACTORY_API_KEY!,
cwd: process.cwd(),
});
console.log(result.text);
Expand All @@ -26,7 +27,10 @@ Send a prompt, get a result, done. The session is created and closed automatical
```ts
import { run } from '@factory/droid-sdk';

const result = await run('What is 2 + 2?', { cwd: process.cwd() });
const result = await run('What is 2 + 2?', {
apiKey: process.env.FACTORY_API_KEY!,
cwd: process.cwd(),
});
console.log(result.text);
```

Expand All @@ -38,6 +42,7 @@ Force the response to match a JSON schema. The validated object is available on
import { OutputFormatType, run } from '@factory/droid-sdk';

const result = await run('Pick a number between 1 and 42.', {
apiKey: process.env.FACTORY_API_KEY!,
cwd: process.cwd(),
outputFormat: {
type: OutputFormatType.JsonSchema,
Expand All @@ -57,16 +62,19 @@ console.log((result.structuredOutput as { number: number }).number);
Create a session once, then call `stream()` multiple times. Context is preserved across turns.

```ts
import { createSession } from '@factory/droid-sdk';
import { createSession, DroidMessageType } from '@factory/droid-sdk';

const session = await createSession({ cwd: process.cwd() });
const session = await createSession({
apiKey: process.env.FACTORY_API_KEY!,
cwd: process.cwd(),
});

for await (const msg of session.stream('Remember the word "mango".')) {
// consume first turn
}

for await (const msg of session.stream('What word did I say?')) {
if (msg.type === 'assistant') console.log(msg.text);
if (msg.type === DroidMessageType.Assistant) console.log(msg.text);
}

await session.close();
Expand All @@ -77,12 +85,14 @@ await session.close();
Reconnect to a previously created session by its ID.

```ts
import { resumeSession } from '@factory/droid-sdk';
import { resumeSession, DroidMessageType } from '@factory/droid-sdk';

const session = await resumeSession('existing-session-id');
const session = await resumeSession('existing-session-id', {
apiKey: process.env.FACTORY_API_KEY!,
});

for await (const msg of session.stream('Continue where we left off.')) {
if (msg.type === 'assistant') console.log(msg.text);
if (msg.type === DroidMessageType.Assistant) console.log(msg.text);
}

await session.close();
Expand All @@ -95,7 +105,10 @@ await session.close();
```ts
import { createSession, DroidMessageType } from '@factory/droid-sdk';

const session = await createSession({ cwd: process.cwd() });
const session = await createSession({
apiKey: process.env.FACTORY_API_KEY!,
cwd: process.cwd(),
});

for await (const msg of session.stream(
'List files in the current directory.'
Expand Down Expand Up @@ -126,7 +139,10 @@ Enable `includePartialMessages` to get token-by-token deltas, thinking blocks, a
```ts
import { createSession, DroidMessageType } from '@factory/droid-sdk';

const session = await createSession({ cwd: process.cwd() });
const session = await createSession({
apiKey: process.env.FACTORY_API_KEY!,
cwd: process.cwd(),
});

for await (const msg of session.stream('Explain recursion.', {
includePartialMessages: true,
Expand All @@ -144,19 +160,25 @@ await session.close();
Use `session.interrupt()` to stop the current turn server-side, or pass an `AbortSignal` to cancel from the client.

```ts
import { createSession } from '@factory/droid-sdk';
import { createSession, DroidMessageType } from '@factory/droid-sdk';

// Interrupt after receiving some output
const session = await createSession({ cwd: process.cwd() });
const session = await createSession({
apiKey: process.env.FACTORY_API_KEY!,
cwd: process.cwd(),
});
for await (const msg of session.stream('Write a long essay.')) {
if (msg.type === 'assistant') {
if (msg.type === DroidMessageType.Assistant) {
await session.interrupt();
}
}
await session.close();

// Or cancel with AbortSignal
const session2 = await createSession({ cwd: process.cwd() });
const session2 = await createSession({
apiKey: process.env.FACTORY_API_KEY!,
cwd: process.cwd(),
});
const controller = new AbortController();
setTimeout(() => controller.abort(), 2000);

Expand All @@ -179,6 +201,7 @@ Define custom tools that Droid can call during a session. Tools are served via a
import {
createSession,
createSdkMcpServer,
DroidMessageType,
tool,
ToolConfirmationOutcome,
} from '@factory/droid-sdk';
Expand All @@ -199,13 +222,14 @@ const server = createSdkMcpServer({
});

const session = await createSession({
apiKey: process.env.FACTORY_API_KEY!,
cwd: process.cwd(),
mcpServers: [server],
permissionHandler: () => ToolConfirmationOutcome.ProceedOnce,
});

for await (const msg of session.stream('Look up Alice.')) {
if (msg.type === 'assistant') console.log(msg.text);
if (msg.type === DroidMessageType.Assistant) console.log(msg.text);
}

await session.close();
Expand All @@ -219,6 +243,7 @@ Control what Droid can do without asking for permission. Set at session creation
import { createSession, AutonomyLevel } from '@factory/droid-sdk';

const session = await createSession({
apiKey: process.env.FACTORY_API_KEY!,
cwd: process.cwd(),
autonomyLevel: AutonomyLevel.High, // Off | Low | Medium | High
});
Expand All @@ -236,6 +261,7 @@ Restrict which tools Droid can use. Accepts tool IDs like `'Read'`, `'Execute'`,
import { createSession } from '@factory/droid-sdk';

const session = await createSession({
apiKey: process.env.FACTORY_API_KEY!,
cwd: process.cwd(),
enabledToolIds: ['Read', 'Grep'],
disabledToolIds: ['Execute'],
Expand All @@ -258,6 +284,7 @@ import {
} from '@factory/droid-sdk';

await run('Create hello.txt with "Hello, World!"', {
apiKey: process.env.FACTORY_API_KEY!,
cwd: process.cwd(),
permissionHandler(params) {
const safe = params.toolUses.every(
Expand All @@ -283,6 +310,7 @@ import {
} from '@factory/droid-sdk';

const session = await createSession({
apiKey: process.env.FACTORY_API_KEY!,
cwd: process.cwd(),
interactionMode: DroidInteractionMode.Spec,
permissionHandler(params) {
Expand All @@ -306,9 +334,12 @@ Send images or documents alongside your prompt. Images must be base64-encoded.

```ts
import { readFileSync } from 'node:fs';
import { createSession } from '@factory/droid-sdk';
import { createSession, DroidMessageType } from '@factory/droid-sdk';

const session = await createSession({ cwd: process.cwd() });
const session = await createSession({
apiKey: process.env.FACTORY_API_KEY!,
cwd: process.cwd(),
});

for await (const msg of session.stream('Describe this image.', {
images: [
Expand All @@ -319,7 +350,7 @@ for await (const msg of session.stream('Describe this image.', {
},
],
})) {
if (msg.type === 'assistant') console.log(msg.text);
if (msg.type === DroidMessageType.Assistant) console.log(msg.text);
}

await session.close();
Expand All @@ -330,17 +361,26 @@ await session.close();
Create a copy of the current session with all context preserved. Useful for branching a conversation.

```ts
import { createSession, resumeSession } from '@factory/droid-sdk';
import {
createSession,
DroidMessageType,
resumeSession,
} from '@factory/droid-sdk';

const session = await createSession({ cwd: process.cwd() });
const session = await createSession({
apiKey: process.env.FACTORY_API_KEY!,
cwd: process.cwd(),
});
for await (const msg of session.stream('Remember: the password is "banana".')) {
}

const { newSessionId } = await session.forkSession();
const fork = await resumeSession(newSessionId);
const fork = await resumeSession(newSessionId, {
apiKey: process.env.FACTORY_API_KEY!,
});

for await (const msg of fork.stream('What is the password?')) {
if (msg.type === 'assistant') console.log(msg.text);
if (msg.type === DroidMessageType.Assistant) console.log(msg.text);
}

await fork.close();
Expand All @@ -354,7 +394,10 @@ Summarize and remove old messages to free up context window space.
```ts
import { createSession } from '@factory/droid-sdk';

const session = await createSession({ cwd: process.cwd() });
const session = await createSession({
apiKey: process.env.FACTORY_API_KEY!,
cwd: process.cwd(),
});

// ... after many turns ...
const result = await session.compactSession();
Expand All @@ -376,7 +419,10 @@ import {
AutonomyLevel,
} from '@factory/droid-sdk';

const transport = new ProcessTransport({ cwd: process.cwd() });
const transport = new ProcessTransport({
cwd: process.cwd(),
env: { FACTORY_API_KEY: process.env.FACTORY_API_KEY! },
});
await transport.connect();
const client = new DroidClient({ transport });

Expand Down Expand Up @@ -423,6 +469,7 @@ Choose which model to use and how much reasoning effort to apply. Configurable a
import { createSession, ReasoningEffort } from '@factory/droid-sdk';

const session = await createSession({
apiKey: process.env.FACTORY_API_KEY!,
cwd: process.cwd(),
modelId: 'claude-sonnet-4-20250514',
reasoningEffort: ReasoningEffort.High,
Expand All @@ -443,7 +490,10 @@ Observe file hooks (pre/post tool execution hooks) as they run during a session.
```ts
import { createSession, DroidMessageType } from '@factory/droid-sdk';

const session = await createSession({ cwd: process.cwd() });
const session = await createSession({
apiKey: process.env.FACTORY_API_KEY!,
cwd: process.cwd(),
});

for await (const msg of session.stream('Create a new file.')) {
if (msg.type === DroidMessageType.Hook) {
Expand All @@ -463,9 +513,10 @@ await session.close();
Programmatically answer questions that Droid asks the user during execution.

```ts
import { createSession } from '@factory/droid-sdk';
import { createSession, DroidMessageType } from '@factory/droid-sdk';

const session = await createSession({
apiKey: process.env.FACTORY_API_KEY!,
cwd: process.cwd(),
askUserHandler(params) {
return {
Expand All @@ -480,7 +531,7 @@ const session = await createSession({
});

for await (const msg of session.stream('Help me set up this project.')) {
if (msg.type === 'assistant') console.log(msg.text);
if (msg.type === DroidMessageType.Assistant) console.log(msg.text);
}

await session.close();
Expand All @@ -493,7 +544,10 @@ Add, remove, toggle, and list MCP servers at runtime within an active session.
```ts
import { createSession, McpServerType } from '@factory/droid-sdk';

const session = await createSession({ cwd: process.cwd() });
const session = await createSession({
apiKey: process.env.FACTORY_API_KEY!,
cwd: process.cwd(),
});

await session.addMcpServer({
name: 'my-server',
Expand All @@ -514,7 +568,10 @@ Query current context window usage to understand how much capacity remains.
```ts
import { createSession } from '@factory/droid-sdk';

const session = await createSession({ cwd: process.cwd() });
const session = await createSession({
apiKey: process.env.FACTORY_API_KEY!,
cwd: process.cwd(),
});
for await (const msg of session.stream('Hello')) {
}

Expand All @@ -533,7 +590,10 @@ Monitor token consumption in real-time via stream events, or read the final tota
```ts
import { createSession, DroidMessageType } from '@factory/droid-sdk';

const session = await createSession({ cwd: process.cwd() });
const session = await createSession({
apiKey: process.env.FACTORY_API_KEY!,
cwd: process.cwd(),
});

for await (const msg of session.stream('Summarize this project.', {
includePartialMessages: true,
Expand All @@ -558,7 +618,10 @@ List all available skills in the current session.
```ts
import { createSession } from '@factory/droid-sdk';

const session = await createSession({ cwd: process.cwd() });
const session = await createSession({
apiKey: process.env.FACTORY_API_KEY!,
cwd: process.cwd(),
});
const { skills } = await session.listSkills();

for (const skill of skills) {
Expand All @@ -575,7 +638,10 @@ Subscribe to raw protocol notifications for custom event handling beyond the str
```ts
import { createSession, SessionNotificationType } from '@factory/droid-sdk';

const session = await createSession({ cwd: process.cwd() });
const session = await createSession({
apiKey: process.env.FACTORY_API_KEY!,
cwd: process.cwd(),
});

const unsubscribe = session.onNotification(
(notification) => {
Expand Down Expand Up @@ -604,7 +670,9 @@ import {
} from '@factory/droid-sdk';

try {
const session = await resumeSession('nonexistent-id');
const session = await resumeSession('nonexistent-id', {
apiKey: process.env.FACTORY_API_KEY!,
});
} catch (error) {
if (error instanceof SessionNotFoundError) {
console.log(`Session not found: ${error.sessionId}`);
Expand Down Expand Up @@ -634,6 +702,7 @@ The package also exports its full Zod schema surface from `src/schemas/index.ts`

| Field | Type | Description |
| :------------------------ | :----------------------- | :---------------------------------------------------------- |
| `apiKey` | `string` | **Required.** Factory API key for authentication |
| `cwd` | `string` | Working directory for the session |
| `machineId` | `string` | Machine identifier for initialization |
| `modelId` | `string` | LLM model identifier |
Expand All @@ -646,6 +715,7 @@ The package also exports its full Zod schema surface from `src/schemas/index.ts`
| `enabledToolIds` | `string[]` | Tool allowlist |
| `disabledToolIds` | `string[]` | Tool denylist |
| `tags` | `SessionTag[]` | Session tags for categorization |
| `sessionSource` | `SessionSource` | Attribution metadata (e.g., integration origin) |
| `permissionHandler` | `PermissionHandler` | Tool confirmation callback |
| `askUserHandler` | `AskUserHandler` | Structured user-input callback |
| `execPath` | `string` | Path to `droid` executable (default: `"droid"`) |
Expand Down
Loading
Loading