Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 14 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
"packages/*"
],
"scripts": {
"build": "bun run --cwd packages/agentic build",
"typecheck": "bun run --cwd packages/agentic typecheck",
"test": "bun run --cwd packages/agentic test"
"build": "bun run --cwd packages/agentic build && bun run --cwd packages/agentic-runtime-local build",
"typecheck": "bun run --cwd packages/agentic typecheck && bun run --cwd packages/agentic-runtime-local typecheck",
"test": "bun run --cwd packages/agentic test && bun run --cwd packages/agentic-runtime-local test"
}
}
27 changes: 27 additions & 0 deletions packages/agentic-runtime-local/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# @tnezdev/agentic-runtime-local

Local runtime package for Agentic workspaces.

The public runtime target is `local`:

```bash
agentic runtime add local
agentic runtime init local
agentic runtime status local
```

This package is intentionally thin. It exports the Agentic runtime manifest,
creates local runtime glue, and provides placeholder `run` behavior while real
execution is split into later runtime work.

Pi may power this runtime under the hood in the future. In this project, Pi
means https://pi.dev/, not Raspberry Pi. `pi` is not a public runtime target.

## Initial Scope

- `init` creates `.agentic/runtime/local/runtime.json` and a local `targets/` directory.
- `run` returns a placeholder result without executing a harness.
- `status` reports whether the local runtime glue has been initialized.

Non-goals for this skeleton: daemon/service mode, scheduling, Cloudflare code,
and a session primitive.
44 changes: 44 additions & 0 deletions packages/agentic-runtime-local/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"name": "@tnezdev/agentic-runtime-local",
"version": "0.5.0",
"description": "Local runtime package for Agentic workspaces",
"license": "MIT",
"type": "module",
"repository": {
"type": "git",
"url": "git+https://github.com/tnezdev/agentic.git",
"directory": "packages/agentic-runtime-local"
},
"publishConfig": {
"access": "public"
},
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"files": [
"dist/**/*.js",
"dist/**/*.d.ts",
"dist/**/*.map",
"README.md"
],
"scripts": {
"build": "tsc -p tsconfig.build.json",
"typecheck": "tsc --noEmit -p tsconfig.json",
"test": "bun test",
"prepack": "bun run build"
},
"dependencies": {},
"peerDependencies": {
"@tnezdev/agentic": "^0.5.0"
},
"devDependencies": {
"@tnezdev/agentic": "^0.5.0",
"@types/bun": "^1.3.11",
"typescript": "^5.7.0"
}
}
103 changes: 103 additions & 0 deletions packages/agentic-runtime-local/src/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { describe, it, expect, beforeEach, afterEach } from "bun:test"
import { mkdtemp, readFile, rm } from "node:fs/promises"
import { join } from "node:path"
import { tmpdir } from "node:os"
import type { RuntimeContext } from "@tnezdev/agentic/runtime"
import { runtime } from "./index.js"

describe("local runtime package", () => {
let tmpDir: string
let ctx: RuntimeContext

beforeEach(async () => {
tmpDir = await mkdtemp(join(tmpdir(), "agentic-runtime-local-test-"))
ctx = {
cwd: tmpDir,
workspace_root: tmpDir,
runtime_name: "local",
runtime_package: "@tnezdev/agentic-runtime-local",
json: true,
env: {},
config: {
adapter: "filesystem",
memory: {
dir: ".agentic/memory",
defaultTier: "L1",
dreamDepth: 3,
},
workflow: {
graphsDir: ".agentic/workflows",
runsDir: ".agentic/runs",
},
wake: {},
runtime: {
targets: {},
},
},
runtime_config: {},
agentic: {} as RuntimeContext["agentic"],
}
})

afterEach(async () => {
await rm(tmpDir, { recursive: true })
})

it("exports a valid local runtime manifest", () => {
expect(runtime.kind).toBe("agentic-runtime")
expect(runtime.api_version).toBe(1)
expect(runtime.name).toBe("local")
expect(runtime.package_name).toBe("@tnezdev/agentic-runtime-local")
expect(runtime.capabilities).toEqual(["init", "run", "status"])
expect(runtime.commands.init).toBeFunction()
expect(runtime.commands.run).toBeFunction()
expect(runtime.commands.status).toBeFunction()
})

it("initializes local runtime glue idempotently", async () => {
const first = await runtime.commands.init!(ctx, { args: [], flags: {} })
const second = await runtime.commands.init!(ctx, { args: [], flags: {} })

expect(first?.summary).toContain("Initialized")
expect(second?.summary).toContain("already initialized")

const state = JSON.parse(
await readFile(join(tmpDir, ".agentic", "runtime", "local", "runtime.json"), "utf-8"),
)
expect(state).toEqual({
version: 1,
runtime: "local",
package_name: "@tnezdev/agentic-runtime-local",
targets_dir: "targets",
})
})

it("reports status before and after init", async () => {
const before = await runtime.commands.status!(ctx, { args: [], flags: {} })
expect(before?.data).toMatchObject({ initialized: false })

await runtime.commands.init!(ctx, { args: [], flags: {} })
const after = await runtime.commands.status!(ctx, { args: [], flags: {} })

expect(after?.summary).toContain("initialized")
expect(after?.data).toMatchObject({
initialized: true,
targets_dir_exists: true,
})
})

it("returns a placeholder run result", async () => {
const result = await runtime.commands.run!(ctx, {
target: "examples/second-brain",
args: ["--dry-run"],
flags: {},
})

expect(result?.summary).toContain("not implemented yet")
expect(result?.data).toMatchObject({
target: "examples/second-brain",
args: ["--dry-run"],
initialized: false,
})
})
})
148 changes: 148 additions & 0 deletions packages/agentic-runtime-local/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { access, mkdir, readFile, stat, writeFile } from "node:fs/promises"
import { join, relative } from "node:path"
import type {
AgenticRuntimePackage,
RuntimeCommandResult,
RuntimeContext,
RuntimeInitArgs,
RuntimeRunArgs,
RuntimeStatusArgs,
} from "@tnezdev/agentic/runtime"

const RUNTIME_NAME = "local"
const PACKAGE_NAME = "@tnezdev/agentic-runtime-local"
const STATE_VERSION = 1
const RUNTIME_DIR = join(".agentic", "runtime", RUNTIME_NAME)
const TARGETS_DIR = "targets"
const STATE_FILE = "runtime.json"

type LocalRuntimeState = {
version: typeof STATE_VERSION
runtime: typeof RUNTIME_NAME
package_name: typeof PACKAGE_NAME
targets_dir: typeof TARGETS_DIR
}

function runtimeDir(ctx: RuntimeContext): string {
return join(ctx.workspace_root, RUNTIME_DIR)
}

function targetsDir(ctx: RuntimeContext): string {
return join(runtimeDir(ctx), TARGETS_DIR)
}

function statePath(ctx: RuntimeContext): string {
return join(runtimeDir(ctx), STATE_FILE)
}

function workspaceRelative(ctx: RuntimeContext, path: string): string {
return relative(ctx.workspace_root, path) || "."
}

async function pathExists(path: string): Promise<boolean> {
try {
await access(path)
return true
} catch {
return false
}
}

async function dirExists(path: string): Promise<boolean> {
try {
return (await stat(path)).isDirectory()
} catch {
return false
}
}

async function readState(ctx: RuntimeContext): Promise<LocalRuntimeState | undefined> {
try {
return JSON.parse(await readFile(statePath(ctx), "utf-8")) as LocalRuntimeState
} catch {
return undefined
}
}

async function initLocalRuntime(
ctx: RuntimeContext,
_args: RuntimeInitArgs,
): Promise<RuntimeCommandResult> {
const dir = runtimeDir(ctx)
const targetDir = targetsDir(ctx)
const path = statePath(ctx)
await mkdir(targetDir, { recursive: true })

const existing = await readState(ctx)
const created = existing === undefined
if (created) {
const state: LocalRuntimeState = {
version: STATE_VERSION,
runtime: RUNTIME_NAME,
package_name: PACKAGE_NAME,
targets_dir: TARGETS_DIR,
}
await writeFile(path, `${JSON.stringify(state, null, 2)}\n`, "utf-8")
}

return {
summary: created
? "Initialized local Agentic runtime glue."
: "Local Agentic runtime glue is already initialized.",
data: {
initialized: true,
created,
config_dir: workspaceRelative(ctx, dir),
state_path: workspaceRelative(ctx, path),
targets_dir: workspaceRelative(ctx, targetDir),
},
}
}

async function runLocalRuntime(
ctx: RuntimeContext,
args: RuntimeRunArgs,
): Promise<RuntimeCommandResult> {
return {
summary: "Local runtime execution is not implemented yet.",
data: {
target: args.target ?? null,
args: args.args,
initialized: await pathExists(statePath(ctx)),
},
}
}

async function statusLocalRuntime(
ctx: RuntimeContext,
_args: RuntimeStatusArgs,
): Promise<RuntimeCommandResult> {
const initialized = await pathExists(statePath(ctx))
return {
summary: initialized
? "Local Agentic runtime glue is initialized."
: "Local runtime package is installed; run `agentic runtime init local` to create local glue.",
data: {
initialized,
state_path: workspaceRelative(ctx, statePath(ctx)),
targets_dir: workspaceRelative(ctx, targetsDir(ctx)),
targets_dir_exists: await dirExists(targetsDir(ctx)),
},
}
}

export const runtime: AgenticRuntimePackage = {
kind: "agentic-runtime",
api_version: 1,
name: RUNTIME_NAME,
package_name: PACKAGE_NAME,
description: "Run Agentic workspaces on the local machine.",
capabilities: ["init", "run", "status"],
commands: {
init: initLocalRuntime,
run: runLocalRuntime,
status: statusLocalRuntime,
},
}

export default runtime
20 changes: 20 additions & 0 deletions packages/agentic-runtime-local/tsconfig.build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": false,
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"allowImportingTsExtensions": false,
"paths": {
"@tnezdev/agentic/runtime": ["../agentic/dist/runtime.d.ts"],
"@tnezdev/agentic": ["../agentic/dist/index.d.ts"]
}
},
"include": ["src/**/*"],
"exclude": ["src/**/*.test.ts"]
}
11 changes: 11 additions & 0 deletions packages/agentic-runtime-local/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@tnezdev/agentic/runtime": ["../agentic/src/runtime.ts"],
"@tnezdev/agentic": ["../agentic/src/index.ts"]
}
},
"include": ["src/**/*"]
}