tardie 0.1.0 → 0.2.0-rc

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -29,27 +29,46 @@ $$\lbrace\mathrm{transitions}\rbrace = f(\mathrm{log})$$
29
29
 
30
30
  ## Quickstart
31
31
 
32
- Install Tardigrade and run the default agent. Use Bun 1.4 or later.
32
+ ### For agents
33
+
34
+ Copy this prompt into your coding agent, or install the [Tardigrade skill](skills/tardigrade/SKILL.md):
35
+
36
+ ```text
37
+ Use https://github.com/clavia-labs/tardigrade and follow skills/tardigrade/SKILL.md to create, author, build, push, and run a local actor. Share its Voyager trace URL.
38
+ ```
39
+
40
+ ### For developers
41
+
42
+ Install Tardigrade and initialize an editable template actor. Use Bun 1.4 or later.
33
43
 
34
44
  ```bash
35
45
  bun add -g tardie
46
+ tdg init researcher
47
+ cd researcher
48
+ ```
49
+
50
+ The `init` command creates `researcher/actor.ts` from the bundled quickstart template. Edit it to describe the agent, then build and push it into the local actor registry:
51
+
52
+ ```bash
53
+ tdg build actor.ts
54
+ tdg push actor.ts --target local
36
55
  tdg dev
37
56
  ```
38
57
 
39
- Start a thread from another shell:
58
+ Keep `tdg dev` running. Start the actor from another shell in the same directory:
40
59
 
41
60
  ```bash
42
- tdg run "read this repo and tell me what it does"
61
+ tdg run "read this repo and tell me what it does" --actor researcher
43
62
  ```
44
63
 
45
- Watch the live trajectory at [localhost:4242](http://localhost:4242).
64
+ Voyager opens at [localhost:4242](http://localhost:4242) by default. `tdg run` prints the direct Voyager URL for the new trace.
46
65
 
47
66
  <picture>
48
67
  <source media="(prefers-color-scheme: dark)" srcset="docs/assets/voyager-dark.png">
49
68
  <img alt="The voyager: a thread's log, one row per event" src="docs/assets/voyager-light.png">
50
69
  </picture>
51
70
 
52
- ## Build one
71
+ ## Build your own harness
53
72
 
54
73
  ```bash
55
74
  bun add tardie
@@ -0,0 +1,49 @@
1
+ import {
2
+ agentOf,
3
+ agentsPackage,
4
+ budget,
5
+ codeModeFor,
6
+ compaction,
7
+ defineActor,
8
+ fetchPackage,
9
+ filesPackage,
10
+ reply,
11
+ workspacePackage
12
+ } from "tardie"
13
+
14
+ // actorName is the stable name used by build, push, and run.
15
+ const actorName = "researcher"
16
+
17
+ // actorInstructions is the main place to describe the job and its expected answer.
18
+ const actorInstructions = `
19
+ You are ${actorName}, a focused research agent.
20
+
21
+ Investigate the user's request carefully.
22
+ Use project files as evidence.
23
+ Delegate independent research when it helps.
24
+ Return a concise answer with concrete findings.
25
+ `.trim()
26
+
27
+ const instructions = {
28
+ name: "instructions",
29
+ system: actorInstructions
30
+ }
31
+
32
+ export default defineActor({
33
+ name: actorName,
34
+ // agentOf combines small capabilities into the actor's behavior.
35
+ actor: agentOf([
36
+ instructions,
37
+ // codeModeFor gives the model one code tool over the packages listed here.
38
+ codeModeFor({
39
+ // packages grant access to local files, HTTP, child agents, and saved tool results.
40
+ packages: [filesPackage(), fetchPackage(), agentsPackage(), workspacePackage()]
41
+ }),
42
+ // reply returns a finished turn to the actor that delegated it.
43
+ reply,
44
+ // budget stops work tools when the turn reaches its tool-call limit.
45
+ budget,
46
+ // compaction summarizes older context when a long turn outgrows its context window.
47
+ compaction
48
+ ])
49
+ })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tardie",
3
- "version": "0.1.0",
3
+ "version": "0.2.0-rc",
4
4
  "license": "MIT",
5
5
  "author": "Clavia, Inc.",
6
6
  "description": "A durable agent harness. State is a pure function of the log.",
@@ -17,7 +17,8 @@
17
17
  },
18
18
  "files": [
19
19
  "src",
20
- "ui"
20
+ "ui",
21
+ "examples"
21
22
  ],
22
23
  "engines": {
23
24
  "bun": ">=1.4.0"
package/src/cli/build.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createHash } from "node:crypto"
2
2
  import { mkdtemp, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises"
3
3
  import { dirname, join, resolve } from "node:path"
4
- import { pathToFileURL } from "node:url"
4
+ import { fileURLToPath, pathToFileURL } from "node:url"
5
5
 
6
6
  import {
7
7
  ACTOR_ARTIFACT_VERSION,
@@ -10,9 +10,12 @@ import {
10
10
  type ActorDefinition
11
11
  } from "tardie"
12
12
 
13
+ import { shellWord } from "./workflow"
14
+
13
15
  export const DEFAULT_BUILD_DIRECTORY = ".tardigrade/build"
14
16
  export const ACTOR_MODULE_FILE = "actor.mjs"
15
17
  export const ACTOR_MANIFEST_FILE = "manifest.json"
18
+ export const TARDIE_ENTRY = fileURLToPath(import.meta.resolve("tardie"))
16
19
 
17
20
  export interface BuildActorOptions {
18
21
  readonly out?: string
@@ -45,6 +48,13 @@ const definitionOf = async (modulePath: string): Promise<ActorDefinition<unknown
45
48
  return candidate as ActorDefinition<unknown>
46
49
  }
47
50
 
51
+ export const tardiePlugin = (entry: string = TARDIE_ENTRY): Bun.BunPlugin => ({
52
+ name: "tardie",
53
+ setup(builder) {
54
+ builder.onResolve({ filter: /^tardie$/ }, () => ({ path: entry }))
55
+ }
56
+ })
57
+
48
58
  export const buildActor = async (entry: string, options: BuildActorOptions = {}): Promise<BuiltActor> => {
49
59
  const cwd = resolve(options.cwd ?? process.cwd())
50
60
  const source = resolve(cwd, entry)
@@ -59,7 +69,8 @@ export const buildActor = async (entry: string, options: BuildActorOptions = {})
59
69
  target: "bun",
60
70
  format: "esm",
61
71
  minify: false,
62
- sourcemap: "none"
72
+ sourcemap: "none",
73
+ plugins: [tardiePlugin()]
63
74
  })
64
75
  if (!result.success) {
65
76
  const detail = result.logs.map((log) => log.message).join("\n")
@@ -99,9 +110,12 @@ export const buildActor = async (entry: string, options: BuildActorOptions = {})
99
110
  }
100
111
  }
101
112
 
102
- export const buildSummary = (built: BuiltActor): string =>
113
+ export const buildSummary = (built: BuiltActor, entry: string): string =>
103
114
  [
104
115
  `built ${built.manifest.name}`,
105
116
  `at ${built.directory}`,
106
- `hash ${built.manifest.digest}`
117
+ `hash ${built.manifest.digest}`,
118
+ "",
119
+ "next",
120
+ ` tdg push ${shellWord(entry)} --target local`
107
121
  ].join("\n")
@@ -7,10 +7,12 @@ import { modelIsConfigured } from "tardie/server/host"
7
7
  import { buildActor, buildSummary, DEFAULT_BUILD_DIRECTORY } from "./build"
8
8
  import { readFileConfig, resolveRemote, resolveServer } from "./config"
9
9
  import { availableDevPort, DEFAULT_MIN_PORT, DEV_URL_HOST, dev, openBrowser } from "./dev"
10
+ import { initActor, initSummary } from "./init"
10
11
  import { DEFAULT_ACTOR_DIRECTORY, pushActor, pushSummary, PUSH_TARGETS } from "./push"
11
12
  import { homeOf, HOME_MISSING, setupJson, setupPrompt, setupSummary, writeSetup } from "./setup"
12
13
  import { actorsTable, threadsTable, DEFAULT_DETAIL_WIDTH, eventsTable, jsonOf, turnLines } from "./render"
13
14
  import { Cli, type CliProjections } from "./services"
15
+ import { traceUrlFor } from "./workflow"
14
16
 
15
17
  // The command tree. Every command is a declaration: its flags, its arguments, and its description
16
18
  // are values, so the help a person reads and the completions a shell installs are generated from
@@ -159,6 +161,36 @@ export const setupCommand = Command.make("setup", { json }, (flags) =>
159
161
  ])
160
162
  )
161
163
 
164
+ export const initCommand = Command.make("init", {
165
+ name: Argument.string("name").pipe(Argument.withDescription("The actor name")),
166
+ dir: Flag.string("dir").pipe(
167
+ Flag.withDescription("The directory to create. Defaults to a directory named after the actor."),
168
+ Flag.optional
169
+ ),
170
+ force: Flag.boolean("force").pipe(
171
+ Flag.withDescription("Replace actor.ts when it already exists."),
172
+ Flag.withDefault(false)
173
+ ),
174
+ json
175
+ }, (flags) =>
176
+ Effect.gen(function*() {
177
+ const directory = stated(flags.dir)
178
+ const initialized = yield* Effect.tryPromise({
179
+ try: () => initActor(flags.name, {
180
+ ...(directory === undefined ? {} : { directory }),
181
+ force: flags.force
182
+ }),
183
+ catch: userErrorOf
184
+ })
185
+ yield* Console.log(flags.json ? jsonOf(initialized) : initSummary(initialized))
186
+ })).pipe(
187
+ Command.withDescription("Create an editable actor from the bundled quickstart."),
188
+ Command.withExamples([
189
+ { command: "tdg init researcher", description: "Create researcher/actor.ts and print the local workflow" },
190
+ { command: "tdg init reviewer --dir actors/reviewer", description: "Create the actor in a stated directory" }
191
+ ])
192
+ )
193
+
162
194
  export const buildCommand = Command.make("build", {
163
195
  entry: Argument.string("entry").pipe(Argument.withDescription("The actor source file to bundle")),
164
196
  out: Flag.string("out").pipe(
@@ -173,7 +205,7 @@ export const buildCommand = Command.make("build", {
173
205
  try: () => buildActor(flags.entry, out === undefined ? {} : { out }),
174
206
  catch: userErrorOf
175
207
  })
176
- yield* Console.log(flags.json ? jsonOf(built) : buildSummary(built))
208
+ yield* Console.log(flags.json ? jsonOf(built) : buildSummary(built, flags.entry))
177
209
  })).pipe(
178
210
  Command.withDescription("Bundle and validate one named actor as a portable artifact."),
179
211
  Command.withExamples([
@@ -346,7 +378,13 @@ export const runCommand = Command.make("run", {
346
378
  const id = stated(flags.id) ?? cli.mintId()
347
379
  const accepted = yield* call(() => client.append(thread, { type: "MessageReceived", id, text: flags.brief }))
348
380
  const view = yield* settle(client, accepted.thread, id, flags.poll, flags.timeout)
349
- yield* Console.log(flags.json ? jsonOf(view) : turnLines(accepted.thread, view))
381
+ yield* Console.log(
382
+ flags.json
383
+ ? jsonOf(view)
384
+ : view.status === SETTLED
385
+ ? `${turnLines(accepted.thread, view)}\n\ntrace\n ${traceUrlFor(client.baseUrl, client.actor, accepted.thread)}`
386
+ : turnLines(accepted.thread, view)
387
+ )
350
388
  if (view.status !== SETTLED) {
351
389
  return yield* Effect.fail(userErrorOf(`turn ${view.turn} on thread ${accepted.thread} is ${view.status}`))
352
390
  }
@@ -444,5 +482,5 @@ export const tdg = Command.make("tdg").pipe(
444
482
  Command.withDescription(
445
483
  "The tardigrade command. Every read is a projection of a durable log, and every failure is the server's own problem document."
446
484
  ),
447
- Command.withSubcommands([setupCommand, buildCommand, pushCommand, devCommand, actorsCommand, runCommand, sendCommand, lsCommand, eventsCommand])
485
+ Command.withSubcommands([setupCommand, initCommand, buildCommand, pushCommand, devCommand, actorsCommand, runCommand, sendCommand, lsCommand, eventsCommand])
448
486
  )
@@ -0,0 +1,65 @@
1
+ import { mkdir, writeFile } from "node:fs/promises"
2
+ import { dirname, relative, resolve } from "node:path"
3
+
4
+ import { actorTemplate } from "./template"
5
+ import { runCommandFor, shellWord } from "./workflow"
6
+
7
+ export const DEFAULT_ACTOR_ENTRY = "actor.ts"
8
+
9
+ export interface InitActorOptions {
10
+ readonly cwd?: string
11
+ readonly directory?: string
12
+ readonly force?: boolean
13
+ }
14
+
15
+ export interface InitializedActor {
16
+ readonly name: string
17
+ readonly directory: string
18
+ readonly entry: string
19
+ }
20
+
21
+ export const defaultInitDirectory = (name: string): string => name
22
+
23
+ const existsError = (error: unknown): boolean =>
24
+ typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST"
25
+
26
+ export const initActor = async (name: string, options: InitActorOptions = {}): Promise<InitializedActor> => {
27
+ const cwd = options.cwd ?? process.cwd()
28
+ const directory = resolve(cwd, options.directory ?? defaultInitDirectory(name))
29
+ const entry = resolve(directory, DEFAULT_ACTOR_ENTRY)
30
+ const source = await actorTemplate({ name })
31
+
32
+ await mkdir(dirname(entry), { recursive: true })
33
+ try {
34
+ await writeFile(entry, source, { encoding: "utf8", flag: options.force === true ? "w" : "wx" })
35
+ } catch (error) {
36
+ if (existsError(error)) {
37
+ throw new Error(`actor already exists at ${entry}. Choose another directory or pass --force.`)
38
+ }
39
+ throw error
40
+ }
41
+
42
+ return { name, directory, entry }
43
+ }
44
+
45
+ const shownPath = (cwd: string, path: string): string => {
46
+ const shown = relative(cwd, path)
47
+ return shown.length === 0 ? "." : shown
48
+ }
49
+
50
+ export const initSummary = (actor: InitializedActor, cwd: string = process.cwd()): string => {
51
+ const directory = shownPath(cwd, actor.directory)
52
+ const entry = shownPath(actor.directory, actor.entry)
53
+ return [
54
+ `created ${shownPath(cwd, actor.entry)}`,
55
+ "",
56
+ "next",
57
+ ` cd ${shellWord(directory)}`,
58
+ ` tdg build ${shellWord(entry)}`,
59
+ ` tdg push ${shellWord(entry)} --target local`,
60
+ " tdg dev",
61
+ "",
62
+ "then, in another terminal",
63
+ ` ${runCommandFor(actor.name)}`
64
+ ].join("\n")
65
+ }
package/src/cli/push.ts CHANGED
@@ -5,6 +5,7 @@ import { join, resolve } from "node:path"
5
5
  import type { ActorArtifactManifest } from "tardie"
6
6
 
7
7
  import { ACTOR_MANIFEST_FILE, ACTOR_MODULE_FILE, buildActor, type BuildActorOptions, type BuiltActor } from "./build"
8
+ import { runCommandFor } from "./workflow"
8
9
 
9
10
  export const DEFAULT_ACTOR_DIRECTORY = ".tardigrade/actors"
10
11
  export const PUSH_PATH = "/v1/actors"
@@ -98,10 +99,22 @@ export const pushActor = async (entry: string, options: PushActorOptions): Promi
98
99
  return options.target === "local" ? pushLocal(built, options) : pushHosted(built, options)
99
100
  }
100
101
 
101
- export const pushSummary = (pushed: PushedActor): string =>
102
- [
102
+ export const pushSummary = (pushed: PushedActor): string => {
103
+ const summary = [
103
104
  `pushed ${pushed.manifest.name}`,
104
105
  `to ${pushed.target}`,
105
106
  `at ${pushed.location}`,
106
107
  `hash ${pushed.manifest.digest}`
107
- ].join("\n")
108
+ ]
109
+ if (pushed.target === "local") {
110
+ summary.push(
111
+ "",
112
+ "next",
113
+ " tdg dev",
114
+ "",
115
+ "then, in another terminal",
116
+ ` ${runCommandFor(pushed.manifest.name)}`
117
+ )
118
+ }
119
+ return summary.join("\n")
120
+ }
@@ -0,0 +1,62 @@
1
+ import { ACTOR_NAME_PATTERN } from "tardie"
2
+ import { existsSync } from "node:fs"
3
+ import { readFile } from "node:fs/promises"
4
+ import { fileURLToPath } from "node:url"
5
+
6
+ export interface ActorTemplateOptions {
7
+ readonly name: string
8
+ readonly instructions?: string
9
+ }
10
+
11
+ export const INSTALLED_ACTOR_TEMPLATE = "../../examples/quickstart/actor.ts"
12
+ export const REPO_ACTOR_TEMPLATE = "../../../examples/quickstart/actor.ts"
13
+ export const ACTOR_TEMPLATE_CANDIDATES: ReadonlyArray<string> = [INSTALLED_ACTOR_TEMPLATE, REPO_ACTOR_TEMPLATE]
14
+
15
+ const templateLiteralOf = (value: string): string =>
16
+ value
17
+ .replaceAll("\\", "\\\\")
18
+ .replaceAll("`", "\\`")
19
+ .replaceAll("${", "\\${")
20
+
21
+ const replaceExactlyOnce = (source: string, pattern: RegExp, replacement: string, field: string): string => {
22
+ const matches = source.match(pattern)
23
+ if (matches === null || matches.length !== 1) throw new Error(`quickstart template must contain one ${field}`)
24
+ return source.replace(pattern, replacement)
25
+ }
26
+
27
+ export const renderActorTemplate = (source: string, options: ActorTemplateOptions): string => {
28
+ if (!ACTOR_NAME_PATTERN.test(options.name)) {
29
+ throw new Error(`actor name must match ${String(ACTOR_NAME_PATTERN)}, got ${JSON.stringify(options.name)}`)
30
+ }
31
+
32
+ let rendered = replaceExactlyOnce(
33
+ source,
34
+ /^const actorName = .+$/gmu,
35
+ `const actorName = ${JSON.stringify(options.name)}`,
36
+ "actorName declaration"
37
+ )
38
+ if (options.instructions === undefined) return rendered
39
+
40
+ const instructions = options.instructions.trim()
41
+ if (instructions.length === 0) throw new Error("actor instructions must not be blank")
42
+ rendered = replaceExactlyOnce(
43
+ rendered,
44
+ /^const actorInstructions = `[\s\S]*?`\.trim\(\)$/gmu,
45
+ `const actorInstructions = \`\n${templateLiteralOf(instructions)}\n\`.trim()`,
46
+ "actorInstructions declaration"
47
+ )
48
+ return rendered
49
+ }
50
+
51
+ export const loadActorTemplate = async (candidates: ReadonlyArray<string>): Promise<string> => {
52
+ const found = candidates.find((candidate) => existsSync(candidate))
53
+ if (found === undefined) throw new Error(`quickstart template is missing. Looked in: ${candidates.join(", ")}`)
54
+ return readFile(found, "utf8")
55
+ }
56
+
57
+ export const actorTemplate = async (
58
+ options: ActorTemplateOptions,
59
+ candidates: ReadonlyArray<string> = ACTOR_TEMPLATE_CANDIDATES.map((candidate) =>
60
+ fileURLToPath(new URL(candidate, import.meta.url))
61
+ )
62
+ ): Promise<string> => renderActorTemplate(await loadActorTemplate(candidates), options)
@@ -0,0 +1,23 @@
1
+ // DEFAULT_ONBOARDING_BRIEF is the first local run suggested after an actor is pushed.
2
+ export const DEFAULT_ONBOARDING_BRIEF = "Read this repository and tell me what it does"
3
+
4
+ // shellWord quotes a generated shell argument when spaces or shell punctuation make a bare word unsafe (workflow.test.ts).
5
+ export const shellWord = (value: string): string =>
6
+ /^[A-Za-z0-9_./-]+$/u.test(value) ? value : `'${value.replaceAll("'", `'\\''`)}'`
7
+
8
+ // runCommandFor renders the local quickstart command with its actor and brief stated.
9
+ export const runCommandFor = (
10
+ actor: string,
11
+ brief: string = DEFAULT_ONBOARDING_BRIEF
12
+ ): string => `tdg run ${shellWord(brief)} --actor ${shellWord(actor)}`
13
+
14
+ // traceUrlFor selects the actor and thread in the Voyager served at the API origin (apps/voyager/src/nav.ts, Route).
15
+ export const traceUrlFor = (baseUrl: string, actor: string, thread: string): string => {
16
+ const url = new URL(baseUrl)
17
+ url.pathname = "/"
18
+ url.search = ""
19
+ url.hash = ""
20
+ url.searchParams.set("actor", actor)
21
+ url.searchParams.set("thread", thread)
22
+ return url.toString()
23
+ }
@@ -0,0 +1 @@
1
+ :root{--bg:#fafaf9;--surface:#fff;--sunken:#f2f2f0;--hair:#e7e7e4;--ink:#1a1c1b;--ink-2:#565a57;--faint:#9a9e9a;--neutral:#59605b;--neutral-wash:#eceeeb;--moss:#4f7a5c;--moss-ink:#3d6249;--moss-wash:#eef3ef;--run:#a07617;--run-wash:#f7f0dd;--ok:#567a5c;--ok-wash:#edf2ee;--wait:#5b6ea8;--wait-wash:#eceff7;--fail:#b3452e;--fail-wash:#f9ece8;--shadow:0 1px 2px #1a1c1b0f, 0 8px 24px #1a1c1b14;--sans:"IBM Plex Sans", system-ui, -apple-system, "Segoe UI", sans-serif;--mono:"IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;--text-meta:12px;--text-dense:13px;--text-read:15px;--text-pane:18px;--text-page:22px;--space-1:4px;--space-2:8px;--space-3:12px;--space-4:16px;--space-5:24px;--space-6:32px;--space-7:48px;--row-h:36px;--gutter:8px;--measure:65ch;--radius:6px;--radius-chip:999px;--ease:cubic-bezier(.23, 1, .32, 1);--fast:.12s;--base:.18s;--breath:2.4s;--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light}@media (prefers-color-scheme:dark){:root:not([data-theme=light]){--bg:#131514;--surface:#1b1d1c;--sunken:#0e100f;--hair:#2a2d2b;--ink:#e8eae8;--ink-2:#a3a8a4;--faint:#6b706c;--neutral:#b8bdb9;--neutral-wash:#272a28;--moss:#7fae8c;--moss-ink:#93c2a0;--moss-wash:#22302a;--run:#d0a83e;--run-wash:#2e2a1c;--ok:#85ad8d;--ok-wash:#1f2a23;--wait:#8ea0d6;--wait-wash:#232838;--fail:#d97757;--fail-wash:#33231e;--shadow:0 1px 2px #0006, 0 8px 24px #0000007a;--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}}:root[data-theme=dark]{--bg:#131514;--surface:#1b1d1c;--sunken:#0e100f;--hair:#2a2d2b;--ink:#e8eae8;--ink-2:#a3a8a4;--faint:#6b706c;--neutral:#b8bdb9;--neutral-wash:#272a28;--moss:#7fae8c;--moss-ink:#93c2a0;--moss-wash:#22302a;--run:#d0a83e;--run-wash:#2e2a1c;--ok:#85ad8d;--ok-wash:#1f2a23;--wait:#8ea0d6;--wait-wash:#232838;--fail:#d97757;--fail-wash:#33231e;--shadow:0 1px 2px #0006, 0 8px 24px #0000007a;--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}*,:before,:after{box-sizing:border-box}html,body,#root{height:100%}body{background:var(--bg);color:var(--ink);font-family:var(--sans);font-size:var(--text-dense);-webkit-font-smoothing:antialiased;margin:0}a{color:var(--moss);text-decoration:none}a:hover{color:var(--moss-ink)}button,input,textarea{font:inherit;color:inherit}:focus-visible{outline:2px solid var(--moss);outline-offset:2px}.mono{font-family:var(--mono);font-variant-numeric:tabular-nums}@keyframes breathe{0%,to{opacity:1}50%{opacity:.55}}.breathe{animation:breathe var(--breath) ease-in-out infinite}@media (prefers-reduced-motion:reduce){.breathe{opacity:1;animation:none}*,:before,:after{transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}}.chip{border-radius:var(--radius-chip);height:18px;font-family:var(--mono);white-space:nowrap;align-items:center;gap:5px;padding:0 7px;font-size:10.5px;display:inline-flex}.chip-running{background:var(--run-wash);color:var(--run)}.chip-settled{background:var(--ok-wash);color:var(--ok)}.chip-blocked{background:var(--wait-wash);color:var(--wait)}.chip-failed{background:var(--fail-wash);color:var(--fail)}.input{border:1px solid var(--hair);border-radius:var(--radius);background:var(--bg);min-width:0;height:32px;font-size:var(--text-meta);outline:none;padding:0 10px}.input:focus{border-color:var(--moss)}.problem{padding:var(--space-3) var(--space-4);border-radius:var(--radius);background:var(--fail-wash);color:var(--fail)}.problem-title{font-weight:600}.problem-detail{margin-top:var(--space-1);font-size:var(--text-meta);color:var(--ink-2)}.reading{font-size:var(--text-read);max-width:var(--measure);line-height:1.6}.rail{background:var(--surface);border-right:1px solid var(--hair);min-height:0;transition:width var(--base) var(--ease);flex-direction:column;flex-shrink:0;display:flex}.rail-head{height:100%;padding:var(--space-3) var(--space-4);justify-content:space-between;align-items:flex-end;display:flex}.rail-wordmark{letter-spacing:-.01em;white-space:nowrap;align-items:baseline;gap:5px;font-size:14px;font-weight:600;display:flex}.rail-product-context{color:var(--faint);letter-spacing:0;font-size:10px;font-weight:400}.rail-section-title,.actor-label{color:var(--faint);letter-spacing:.07em;text-transform:uppercase;font-size:10.5px}.actor-rail{border-right:1px solid var(--hair);background:var(--bg);flex-direction:column;flex-shrink:0;min-height:0;display:flex}.pane-chrome{border-bottom:1px solid var(--hair);flex-shrink:0;overflow:hidden}.actor-head{height:100%;padding:var(--space-3) var(--space-4);justify-content:space-between;align-items:flex-start;display:flex}.actor-identity{flex-direction:column;justify-content:space-between;height:100%;display:flex}.actor-search-wrap{padding:10px var(--space-3)}.actor-search{width:100%;height:30px}.actor-problem{margin:0 var(--space-3) var(--space-3)}.actor-list{min-height:0;padding:0 var(--space-2) var(--space-3);flex:1;overflow-y:auto}.actor-row{width:100%;min-height:var(--row-h);align-items:center;gap:var(--space-2);padding:0 var(--space-2);border-radius:var(--radius);color:var(--ink-2);cursor:pointer;text-align:left;transition:background var(--fast) var(--ease), color var(--fast) var(--ease), transform var(--fast) var(--ease);background:0 0;border:0;display:flex}.actor-row:hover{background:var(--sunken);color:var(--ink)}.actor-row:active{transform:scale(.97)}.actor-row-selected,.actor-row-selected:hover{background:var(--moss-wash);color:var(--moss-ink)}.actor-name{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.actor-kind{color:var(--faint);flex-shrink:0;margin-left:auto;font-size:9.5px}.actor-mark-list{align-items:center;gap:var(--space-1);min-height:0;padding:var(--space-2) 0;flex-direction:column;flex:1;display:flex;overflow-y:auto}.actor-mark-button{width:100%;min-height:var(--row-h);cursor:pointer;background:0 0;border:0;place-items:center;padding:0;display:grid}.actor-mark{border-radius:var(--radius);background:var(--sunken);color:var(--ink-2);transition:color var(--fast) var(--ease), transform var(--fast) var(--ease);place-items:center;font-size:9.5px;display:grid}.actor-mark-button:hover .actor-mark{color:var(--ink)}.actor-mark-button:active .actor-mark{transform:scale(.97)}.actor-mark-selected .actor-mark,.actor-mark-selected:hover .actor-mark{background:var(--moss-wash);color:var(--moss-ink)}.actor-footer{padding:var(--space-2);border-top:1px solid var(--hair);flex-direction:column;flex-shrink:0;gap:2px;margin-top:auto;display:flex}.actor-api{width:100%;min-height:var(--row-h);align-items:center;gap:var(--space-2);padding:0 var(--space-2);border-radius:var(--radius);color:var(--ink-2);cursor:pointer;text-align:left;transition:background var(--fast) var(--ease), color var(--fast) var(--ease), transform var(--fast) var(--ease);background:0 0;border:0;display:flex}.actor-api:hover{background:var(--sunken);color:var(--ink)}.actor-api-active,.actor-api-active:hover{background:var(--moss-wash);color:var(--moss-ink)}.actor-api:active{transform:scale(.97)}.actor-rail-collapsed .actor-api{justify-content:center;padding:0}.api-view{flex:1;min-width:0;height:100%;display:flex;overflow:hidden}.api-nav{background:var(--bg);border-right:1px solid var(--hair);flex-direction:column;min-width:0;display:flex}.api-search-wrap{align-items:center;gap:var(--space-2);height:34px;margin:10px var(--space-3);border:1px solid var(--hair);border-radius:var(--radius);background:var(--bg);color:var(--faint);flex-shrink:0;padding:0 10px;display:flex}.api-search-wrap:focus-within{border-color:var(--moss)}.api-search-wrap input{width:100%;min-width:0;color:var(--ink);background:0 0;border:0;outline:0;padding:0}.api-nav-list{min-height:0;padding:0 var(--space-2) var(--space-3);flex:1;overflow-y:auto}.api-overview-link,.api-nav-row{width:100%;min-height:var(--row-h);align-items:center;gap:var(--space-2);padding:0 var(--space-2);border-radius:var(--radius);color:var(--ink-2);cursor:pointer;text-align:left;transition:background var(--fast) var(--ease), color var(--fast) var(--ease), transform var(--fast) var(--ease);background:0 0;border:0;display:flex}.api-overview-link:hover,.api-nav-row:hover{background:var(--sunken);color:var(--ink)}.api-overview-link:active,.api-nav-row:active{transform:scale(.97)}.api-nav-selected,.api-nav-selected:hover{background:var(--moss-wash);color:var(--moss-ink)}.api-group h2{margin:var(--space-4) var(--space-2) var(--space-1);color:var(--faint);letter-spacing:.07em;text-transform:uppercase;font-size:10.5px;font-weight:400}.api-method{justify-content:center;min-width:42px;font-size:10.5px;font-weight:500;display:inline-flex}.api-method-get{color:var(--wait)}.api-method-post{color:var(--ok)}.api-method-put,.api-method-patch{color:var(--run)}.api-method-delete{color:var(--fail)}.api-nav-path{min-width:0;color:inherit;text-overflow:ellipsis;white-space:nowrap;font-size:11.5px;overflow:hidden}.api-reading{background:var(--bg);flex:1;min-width:0;overflow-y:auto}.api-detail{width:min(1240px,100%);padding:72px var(--space-6) 96px;margin:0 auto}.api-detail-head{padding:var(--space-7) 0 var(--space-5)}.api-operation-title{align-items:center;gap:var(--space-3);display:flex}.api-operation-title h1,.api-overview h1{color:var(--ink);font-size:var(--text-page);letter-spacing:-.02em;margin:0;font-weight:500}.api-operation-detail h1{margin:var(--space-2) 0 0;color:var(--ink);letter-spacing:-.03em;text-transform:lowercase;font-size:30px;font-weight:500}.api-resource{padding-top:var(--space-7);scroll-margin-top:var(--space-5)}.api-resource+.api-resource{margin-top:var(--space-7);border-top:1px solid var(--hair)}.api-endpoint{scroll-margin-top:var(--space-5)}.api-endpoint+.api-endpoint{margin-top:var(--space-7);padding-top:var(--space-7);border-top:1px solid var(--hair)}.api-detail-head h2{color:var(--ink);letter-spacing:-.025em;margin:0;font-size:25px;font-weight:500}.api-operation-id,.api-eyebrow{margin-top:var(--space-2);color:var(--faint);font-size:11px}.api-detail p{max-width:var(--measure);color:var(--ink-2);line-height:1.6}.api-section{padding:var(--space-5) 0;border-bottom:1px solid var(--hair)}.api-section h2,.api-section h3{margin:0 0 var(--space-4);color:var(--ink);font-size:var(--text-read);font-weight:500}.api-tag-overview{align-items:start;gap:var(--space-7);min-height:220px;padding-bottom:var(--space-7);border-bottom:1px solid var(--hair);grid-template-columns:minmax(220px,1fr) minmax(360px,1fr);display:grid}.api-tag-overview .api-eyebrow{color:var(--faint);letter-spacing:.07em;text-transform:uppercase;margin:0;font-size:10.5px}.api-operations-card,.api-code-card{border:1px solid var(--hair);border-radius:var(--radius);background:var(--surface);overflow:hidden}.api-card-title,.api-code-head{min-height:38px;padding:0 var(--space-3);border-bottom:1px solid var(--hair);color:var(--ink-2);font-size:12px}.api-card-title{align-items:center;font-weight:500;display:flex}.api-operations-list{padding:var(--space-2)}.api-operations-list button{align-items:center;gap:var(--space-2);width:100%;min-height:32px;padding:0 var(--space-2);border-radius:var(--radius);color:var(--ink-2);cursor:pointer;text-align:left;background:0 0;border:0;display:flex}.api-operations-list button:hover{background:var(--sunken);color:var(--ink)}.api-operations-list button:active{transform:scale(.97)}.api-operation-grid{gap:var(--space-7);grid-template-columns:minmax(0,1fr) minmax(360px,.95fr);display:grid}.api-operation-docs{min-width:0}.api-operation-examples{top:var(--space-5);align-self:start;gap:var(--space-3);display:grid;position:sticky}.api-code-head{justify-content:space-between;align-items:center;gap:var(--space-3);display:flex}.api-code-head>div{align-items:center;gap:var(--space-2);min-width:0;display:flex}.api-code-head>span{color:var(--faint);font-size:10.5px}.api-code-card pre{max-height:360px;padding:var(--space-4);background:var(--sunken);color:var(--ink-2);white-space:pre-wrap;word-break:break-word;margin:0;font-size:11.5px;line-height:1.65;overflow:auto}.api-parameters,.api-responses,.api-content-list{gap:var(--space-3);display:grid}.api-parameter,.api-response,.api-content{padding:var(--space-3);border:1px solid var(--hair);border-radius:var(--radius);background:var(--surface)}.api-operation-docs .api-section{padding:var(--space-5) 0}.api-operation-docs .api-response{border:0;border-bottom:1px solid var(--hair);background:0 0;border-radius:0;padding:0}.api-operation-docs .api-response:first-child{border-top:1px solid var(--hair)}.api-operation-docs .api-response-head{cursor:pointer;min-height:52px;list-style:none}.api-operation-docs .api-response-head::-webkit-details-marker{display:none}.api-operation-docs .api-response-head:before{border-right:1px solid var(--faint);border-bottom:1px solid var(--faint);content:"";width:7px;height:7px;transition:transform var(--fast) var(--ease);transform:rotate(-45deg)}.api-operation-docs .api-response[open] .api-response-head:before{transform:rotate(45deg)}.api-operation-docs .api-response .api-content-list{margin:0 0 var(--space-4) 22px}.api-operation-docs .api-response .api-content{background:0 0;border:0}.api-operation-docs .api-request-section .api-content{padding:var(--space-3) 0;border:0;border-top:1px solid var(--hair);border-bottom:1px solid var(--hair);background:0 0;border-radius:0}.api-operation-docs .api-request-section .api-media-type{margin-bottom:var(--space-3)}.api-operation-docs .api-parameters-section .api-parameter{padding:var(--space-3) 0;border:0;border-top:1px solid var(--hair);background:0 0;border-radius:0}.api-operation-docs .api-parameters-section .api-parameter:last-child{border-bottom:1px solid var(--hair)}.api-headers-section{padding-top:0}.api-header-line{border-top:1px solid var(--hair);grid-template-columns:120px minmax(0,1fr);align-items:center;min-height:44px;display:grid}.api-header-line:last-child{border-bottom:1px solid var(--hair)}.api-header-example{color:var(--faint);font-size:11.5px}.api-parameter{align-items:center;gap:var(--space-3);grid-template-columns:minmax(140px,1fr) minmax(100px,auto) auto;display:grid}.api-parameter p{grid-column:1/-1;margin:0}.api-parameter-in{margin-left:var(--space-2);color:var(--faint);font-size:10.5px}.api-response-head{align-items:center;gap:var(--space-3);display:flex}.api-status{color:var(--fail);font-weight:500}.api-status-ok{color:var(--ok)}.api-response .api-content-list{margin-top:var(--space-3)}.api-media-type{margin-bottom:var(--space-2);color:var(--faint);font-size:11px}.api-type,.api-property-type{color:var(--wait);font-size:11.5px}.api-properties{margin-top:var(--space-2);border-top:1px solid var(--hair)}.api-property{padding:10px 0 2px var(--space-3);border-left:1px solid var(--hair)}.api-property-line{align-items:baseline;gap:var(--space-2);display:flex}.api-property-name{color:var(--ink);font-size:12px}.api-required,.api-optional{color:var(--faint);margin-left:auto;font-size:10px}.api-required{color:var(--run)}.api-schema-cut{margin-top:var(--space-2);color:var(--faint);font-size:11px}.api-overview{padding:var(--space-4) 0 var(--space-7);scroll-margin-top:var(--space-5);border-bottom:1px solid var(--hair)}.api-overview .api-eyebrow{margin:0 0 var(--space-2);letter-spacing:.07em;text-transform:uppercase}.api-version{margin-top:var(--space-2);color:var(--faint);font-size:11px;display:inline-block}.api-facts{margin-top:var(--space-6);border-top:1px solid var(--hair);gap:0;display:grid}.api-facts div{padding:var(--space-3) 0;border-bottom:1px solid var(--hair);grid-template-columns:140px minmax(0,1fr);display:grid}.api-facts dt{color:var(--faint)}.api-facts dd{overflow-wrap:anywhere;margin:0}.api-load-problem{margin:var(--space-7)}@media (width<=760px){.api-detail{padding-right:var(--space-4);padding-left:var(--space-4)}}@media (width<=1100px){.api-tag-overview,.api-operation-grid{grid-template-columns:1fr}.api-tag-overview{min-height:0}.api-operation-examples{position:static}}.actor-rail-collapsed .actor-only{display:none}.actor-rail-collapsed .actor-head{justify-content:center}.rail-search{width:100%;height:30px}.run-list{min-height:0;padding:0 var(--space-2) var(--space-3);flex:1;overflow-y:auto}.rail-row{padding:var(--space-2) 14px;border-radius:var(--radius);cursor:pointer;transition:background var(--fast) var(--ease);flex-direction:column;gap:2px;display:flex}.rail-row:hover{background:var(--sunken)}.rail-row-selected,.rail-row-selected:hover{background:var(--moss-wash)}.rail-id{white-space:nowrap;text-overflow:ellipsis;flex:1;min-width:0;font-size:12.5px;overflow:hidden}.rail-meta{color:var(--faint);font-size:11px}.thread-head{align-items:center;gap:var(--space-2);padding:var(--space-4) calc(var(--space-5) + 34px) 10px var(--space-5);display:flex}.icon-btn{border-radius:var(--radius);width:26px;height:26px;color:var(--faint);cursor:pointer;transition:color var(--fast) var(--ease);background:0 0;border:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.icon-btn:hover{color:var(--ink-2)}.icon-btn:active{transform:scale(.97)}.icon-btn-done{color:var(--ok)}.window{margin:0 var(--space-5) 10px;-webkit-user-select:none;user-select:none}.window-head{justify-content:space-between;align-items:baseline;margin-bottom:6px;display:flex}.window-eyebrow{color:var(--faint);text-transform:uppercase;letter-spacing:.06em;font-size:11px}.window-actions{align-items:center;gap:var(--space-2);display:flex}.window-readout{color:var(--ink-2);font-size:11.5px}.window-count{color:var(--faint)}.window-track{position:relative}.window-bars{align-items:flex-end;gap:1px;display:flex;position:absolute;inset:0}.window-bar{background:var(--hair);transition:background var(--fast) var(--ease);border-radius:1px 1px 0 0;flex:1}.window-bar-inside{background:var(--moss);opacity:.55}.window-selection{background:color-mix(in oklab, var(--moss) 12%, transparent);border-left:2px solid var(--moss);border-right:2px solid var(--moss);pointer-events:none;position:absolute;top:0;bottom:0}.window-handle{cursor:ew-resize;width:12px;margin-left:-6px;position:absolute;top:0;bottom:0}.window-grip{background:var(--moss);width:2px;height:100%;margin:0 auto}.window-marks{margin-top:var(--space-1);color:var(--faint);justify-content:space-between;font-size:10px;display:flex}.stream-note{padding:0 var(--space-5) var(--space-2);color:var(--faint);font-size:11px}.thread-view{flex:1;min-width:0;min-height:0;display:flex}.thread-trace{flex-direction:column;flex:1;min-width:0;min-height:0;display:flex}.events{min-width:0;padding:0 var(--space-4) var(--space-4);flex:1;overflow-y:auto}.event-row{align-items:flex-start;gap:var(--space-3);padding:9px var(--space-2);border-radius:var(--radius);cursor:pointer;transition:background var(--fast) var(--ease);display:flex}.event-row:hover{background:var(--sunken)}.event-row-selected,.event-row-selected:hover{background:var(--moss-wash)}.event-stamp{border-radius:var(--radius-chip);white-space:nowrap;flex-shrink:0;align-items:center;height:24px;padding:0 10px;font-size:12px;display:inline-flex}.event-text{min-width:0;color:var(--ink);white-space:nowrap;text-overflow:ellipsis;flex:1;font-size:14.5px;line-height:1.55;overflow:hidden}.event-time{padding-left:var(--space-4);color:var(--faint);flex-shrink:0;margin-top:4px;margin-left:auto;font-size:11px}.event-inspector{border-left:1px solid var(--hair);background:var(--bg);flex:0 auto;min-width:0;overflow-y:auto}.event-inspector-head{padding:0 var(--space-4);border-bottom:1px solid var(--hair);flex-shrink:0;justify-content:space-between;align-items:center;display:flex}.event-inspector-title{align-items:center;gap:var(--space-2);min-width:0;display:flex}.event-inspector-seq{color:var(--faint);white-space:nowrap;font-size:11px}.event-detail{margin:var(--space-4);padding-left:var(--space-3);border-left:1px solid var(--hair);grid-template-columns:max-content minmax(0,1fr);gap:5px 20px;display:grid}.event-key{color:var(--faint);font-size:11.5px}.event-value{color:var(--ink);white-space:pre-wrap;word-break:break-word;font-size:12.5px;display:block}.event-value-frame{min-width:0}.field-more{background:linear-gradient(to bottom, transparent, var(--bg) 68%);justify-content:center;margin-top:-64px;padding:46px 0 4px;display:flex;position:relative}.field-toggle{min-height:28px;margin:var(--space-2) auto 0;padding:0 var(--space-3);border:1px solid var(--hair);border-radius:var(--radius-chip);background:var(--surface);color:var(--ink-2);cursor:pointer;font-family:var(--sans);font-size:var(--text-meta);box-shadow:var(--shadow);transition:color var(--fast) var(--ease), transform var(--fast) var(--ease);display:block}.field-toggle:hover{color:var(--ink)}.field-toggle:active{transform:scale(.97)}.event-code{background:var(--sunken);border-radius:4px;padding:8px 10px;line-height:1.5}.event-json{tab-size:2;line-height:1.55}.pane-empty{padding:var(--space-7) 0;text-align:center;font-size:var(--text-meta);color:var(--faint)}.quickstart-empty{min-height:0;padding:var(--space-6);flex:1;place-items:center;display:grid}.quickstart{width:min(720px,100%)}.quickstart-intro{margin-bottom:var(--space-5);text-align:center}.quickstart-intro h1{margin:0 0 var(--space-2);font-size:var(--text-page);letter-spacing:-.02em;font-weight:500}.quickstart-intro p{color:var(--ink-2);font-size:var(--text-read);margin:0}.quickstart-grid{gap:var(--space-3);display:grid}.quickstart-card{border:1px solid var(--hair);border-radius:var(--radius);background:var(--surface);box-shadow:var(--shadow);overflow:hidden}.quickstart-card-head{min-height:38px;padding:0 var(--space-3) 0 var(--space-4);border-bottom:1px solid var(--hair);justify-content:space-between;align-items:center;display:flex}.quickstart-label{color:var(--ink-2);text-transform:uppercase;letter-spacing:.06em;font-size:11px}.quickstart-copy{min-height:26px;padding:0 var(--space-2);border-radius:var(--radius);color:var(--faint);cursor:pointer;font-size:var(--text-meta);transition:color var(--fast) var(--ease), transform var(--fast) var(--ease);background:0 0;border:0;align-items:center;gap:6px;display:inline-flex}.quickstart-copy:hover{color:var(--ink-2)}.quickstart-copy:active{transform:scale(.97)}.quickstart-copy-done{color:var(--ok)}.quickstart-code{padding:var(--space-4);background:var(--sunken);color:var(--ink);font-family:var(--mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:0;font-size:12.5px;line-height:1.6;overflow-x:auto}@media (width<=640px){.quickstart-empty{padding:var(--space-5) var(--space-4);align-items:start}}