@nuucognition/flint-cli 0.6.0-dev.20 → 0.6.0-dev.21
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 +17 -0
- package/bin/flint-dev.js +15 -0
- package/bin/flint-prod.js +7 -0
- package/bin/flint.js +4 -84
- package/bin/launch-flint.js +93 -0
- package/bin/live-build-path.js +2 -1
- package/dist/_flint/prompts/flint-interactive.md +6 -3
- package/dist/_orbh/prompts/harness/claude.md +13 -5
- package/dist/_orbh/prompts/harness/codex.md +1 -1
- package/dist/_orbh/prompts/harness/droid.md +1 -1
- package/dist/_orbh/prompts/harness/opencode.md +1 -1
- package/dist/_orbh/prompts/headless.md +15 -9
- package/dist/_orbh/prompts/peer.md +5 -2
- package/dist/_orbh/prompts/subagent.md +7 -2
- package/dist/index.js +77931 -41140
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -154,6 +154,23 @@ flint open my-project # Open by name from registry
|
|
|
154
154
|
|
|
155
155
|
**Dev-only:** use the `flint-dev` binary or a source build with no baked `BUILD_MODE`.
|
|
156
156
|
|
|
157
|
+
### Development builds and atomic live releases
|
|
158
|
+
|
|
159
|
+
Flint has separate development and live runtime channels:
|
|
160
|
+
|
|
161
|
+
- `ndv use flint dev` activates `bin/flint-dev.js`. It consumes the checkout's
|
|
162
|
+
`apps/flint-cli/dist`, so `ndv build flint dev` is visible on the next
|
|
163
|
+
invocation. Orbh children spawned from this channel remain on the caller's
|
|
164
|
+
checkout.
|
|
165
|
+
- `bin/flint.js` is the canonical live launcher used by Orbh-managed release
|
|
166
|
+
flows. When `.flint-live-build/current` is complete, it consumes that
|
|
167
|
+
atomically selected release rather than an in-place checkout build.
|
|
168
|
+
- `bin/flint-prod.js` consumes the `dist` shipped inside its installed package.
|
|
169
|
+
|
|
170
|
+
The channels are intentionally distinct: NDV owns checkout development, while
|
|
171
|
+
the declared `[live-build]` command stages and smoke-tests long-running Orbh
|
|
172
|
+
releases before cutover.
|
|
173
|
+
|
|
157
174
|
**Behavior:**
|
|
158
175
|
- Opens the flint in all apps configured in the current profile config
|
|
159
176
|
- Default (no config): opens in Obsidian only
|
package/bin/flint-dev.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
import { launchFlint } from './launch-flint.js';
|
|
6
|
+
|
|
7
|
+
// A dev activation is an explicit choice to keep the spawned Orbh process tree
|
|
8
|
+
// on the caller's checkout instead of silently switching detached auxiliaries
|
|
9
|
+
// back to the machine-canonical live launcher.
|
|
10
|
+
process.env.ORBH_AUX_CODE_SOURCE = 'caller';
|
|
11
|
+
|
|
12
|
+
await launchFlint({
|
|
13
|
+
launcherPath: fileURLToPath(import.meta.url),
|
|
14
|
+
preferLiveBuild: false,
|
|
15
|
+
});
|
package/bin/flint-prod.js
CHANGED
|
@@ -30,6 +30,13 @@ if (openTuiRequested && ffiSupported
|
|
|
30
30
|
|
|
31
31
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
32
32
|
const entrypoint = join(__dirname, '..', 'dist', 'index.js');
|
|
33
|
+
|
|
34
|
+
// Default to production React — see bin/flint.js. The bundled react-reconciler
|
|
35
|
+
// is baked to production at build time, but `react` stays external; with
|
|
36
|
+
// NODE_ENV unset it loads its development build, whose jsx runtime calls
|
|
37
|
+
// dev-only dispatcher methods the production reconciler never installs
|
|
38
|
+
// ("dispatcher.getOwner is not a function" on first TUI render).
|
|
39
|
+
process.env.NODE_ENV ||= 'production';
|
|
33
40
|
process.env.FLINT_CLI_LAUNCHER ??= fileURLToPath(import.meta.url);
|
|
34
41
|
process.env.FLINT_CLI_ENTRYPOINT ??= entrypoint;
|
|
35
42
|
await import(pathToFileURL(entrypoint).href);
|
package/bin/flint.js
CHANGED
|
@@ -1,90 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { existsSync } from 'node:fs';
|
|
4
|
-
import { spawnSync } from 'node:child_process';
|
|
5
3
|
import { fileURLToPath } from 'node:url';
|
|
6
|
-
import { dirname, join } from 'node:path';
|
|
7
|
-
import { resolveLiveBuildPackageRoot } from './live-build-path.js';
|
|
8
4
|
|
|
9
|
-
|
|
10
|
-
const sourcePackageRoot = join(__dirname, '..');
|
|
11
|
-
const runtimePackageRoot = resolveLiveBuildPackageRoot(sourcePackageRoot);
|
|
12
|
-
const openTuiInvocationEntry = join(runtimePackageRoot, 'dist', 'open-tui-invocation.js');
|
|
13
|
-
const { shouldEnableOpenTuiFfi } = await import(openTuiInvocationEntry);
|
|
5
|
+
import { launchFlint } from './launch-flint.js';
|
|
14
6
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
envRenderer: process.env.ORBH_TUI_RENDERER,
|
|
19
|
-
envNoChrome: process.env.ORBH_NO_CHROME,
|
|
20
|
-
stdinTty: Boolean(process.stdin.isTTY),
|
|
21
|
-
stdoutTty: Boolean(process.stdout.isTTY),
|
|
7
|
+
await launchFlint({
|
|
8
|
+
launcherPath: fileURLToPath(import.meta.url),
|
|
9
|
+
preferLiveBuild: true,
|
|
22
10
|
});
|
|
23
|
-
const [nodeMajor = 0, nodeMinor = 0] = process.versions.node.split('.').map(Number);
|
|
24
|
-
const ffiSupported = nodeMajor > 26 || (nodeMajor === 26 && nodeMinor >= 1);
|
|
25
|
-
const ffiFlag = '--experimental-ffi';
|
|
26
|
-
if (openTuiRequested && ffiSupported
|
|
27
|
-
&& !process.execArgv.includes(ffiFlag)
|
|
28
|
-
&& !process.env.NODE_OPTIONS?.split(/\s+/).includes(ffiFlag)
|
|
29
|
-
&& process.execve) {
|
|
30
|
-
const options = new Set(process.env.NODE_OPTIONS?.split(/\s+/).filter(Boolean) ?? []);
|
|
31
|
-
for (const option of [ffiFlag, '--disable-warning=ExperimentalWarning', '--disable-warning=DEP0205']) options.add(option);
|
|
32
|
-
process.execve(process.execPath, process.argv, {
|
|
33
|
-
...process.env,
|
|
34
|
-
NODE_OPTIONS: [...options].join(' '),
|
|
35
|
-
ORBH_TUI_FFI_REEXEC: '1',
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const srcEntry = join(__dirname, '..', 'src', 'index.ts');
|
|
40
|
-
const distEntry = join(runtimePackageRoot, 'dist', 'index.js');
|
|
41
|
-
|
|
42
|
-
// Default to production React/ink. Unset NODE_ENV loads react-reconciler's
|
|
43
|
-
// development build, whose per-render performance.measure() calls accumulate
|
|
44
|
-
// forever in Node's perf buffer — a ~1GB/hour leak in long-lived interactive
|
|
45
|
-
// views like `orbh list`. Explicit NODE_ENV=development still wins.
|
|
46
|
-
process.env.NODE_ENV ||= 'production';
|
|
47
|
-
|
|
48
|
-
// Performance: prefer the built dist and run it IN-PROCESS (one node process,
|
|
49
|
-
// no tsx, no recompile-on-every-invocation). `flint orbh i` previously spawned
|
|
50
|
-
// `node bin/flint.js` -> `tsx` -> `tsx loader (compiling src/index.ts)` -> agent,
|
|
51
|
-
// i.e. 3 node processes + a TypeScript compile per launch. Running the prebuilt
|
|
52
|
-
// dist in-process collapses that to a single node process with zero compile cost.
|
|
53
|
-
// Set FLINT_CLI_FORCE_SOURCE=1 to fall back to running the TypeScript source via
|
|
54
|
-
// tsx (the dev workflow where you edit src/ without rebuilding).
|
|
55
|
-
const forceSource =
|
|
56
|
-
process.env.FLINT_CLI_FORCE_SOURCE === '1' ||
|
|
57
|
-
process.env.FLINT_CLI_FORCE_SOURCE === 'true';
|
|
58
|
-
|
|
59
|
-
if (!forceSource && existsSync(distEntry)) {
|
|
60
|
-
process.env.FLINT_CLI_LAUNCHER = fileURLToPath(import.meta.url);
|
|
61
|
-
process.env.FLINT_CLI_ENTRYPOINT = distEntry;
|
|
62
|
-
// The dist entry parses process.argv and runs the CLI on import. Awaiting keeps
|
|
63
|
-
// the launcher process alive for the CLI's async work (incl. interactive `orbh i`,
|
|
64
|
-
// which blocks on the child process's exit event — no polling, no extra wrapper).
|
|
65
|
-
await import(distEntry);
|
|
66
|
-
} else {
|
|
67
|
-
// Dev fallback: run the TypeScript source via tsx in a subprocess.
|
|
68
|
-
const tsxPath = join(__dirname, '..', 'node_modules', '.bin', 'tsx');
|
|
69
|
-
|
|
70
|
-
// tsx still calls the deprecated module.register(); Node 26 turned DEP0205 into a
|
|
71
|
-
// runtime warning. Disable it just for the spawned subprocess, preserving any
|
|
72
|
-
// NODE_OPTIONS the user already set. Remove once tsx migrates to module.registerHooks().
|
|
73
|
-
const SUPPRESS_FLAG = '--disable-warning=DEP0205';
|
|
74
|
-
const existingNodeOptions = process.env.NODE_OPTIONS ?? '';
|
|
75
|
-
const nodeOptions = existingNodeOptions.includes(SUPPRESS_FLAG)
|
|
76
|
-
? existingNodeOptions
|
|
77
|
-
: (existingNodeOptions ? `${existingNodeOptions} ${SUPPRESS_FLAG}` : SUPPRESS_FLAG);
|
|
78
|
-
|
|
79
|
-
const result = spawnSync(tsxPath, [srcEntry, ...process.argv.slice(2)], {
|
|
80
|
-
stdio: 'inherit',
|
|
81
|
-
env: {
|
|
82
|
-
...process.env,
|
|
83
|
-
NODE_OPTIONS: nodeOptions,
|
|
84
|
-
FLINT_CLI_LAUNCHER: fileURLToPath(import.meta.url),
|
|
85
|
-
FLINT_CLI_ENTRYPOINT: srcEntry,
|
|
86
|
-
},
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
process.exit(result.status ?? 0);
|
|
90
|
-
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
import { resolveLiveBuildPackageRoot } from './live-build-path.js';
|
|
7
|
+
|
|
8
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const sourcePackageRoot = join(__dirname, '..');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Run Flint through an explicit runtime channel.
|
|
13
|
+
*
|
|
14
|
+
* NDV dev launchers consume the checkout build. Canonical live launchers prefer
|
|
15
|
+
* the atomically selected release. Keeping this decision in the entrypoint
|
|
16
|
+
* avoids an inherited environment variable silently changing channels.
|
|
17
|
+
*/
|
|
18
|
+
export async function launchFlint({ launcherPath, preferLiveBuild }) {
|
|
19
|
+
const runtimePackageRoot = resolveLiveBuildPackageRoot(sourcePackageRoot, { preferLiveBuild });
|
|
20
|
+
const openTuiInvocationEntry = join(runtimePackageRoot, 'dist', 'open-tui-invocation.js');
|
|
21
|
+
const { shouldEnableOpenTuiFfi } = await import(openTuiInvocationEntry);
|
|
22
|
+
|
|
23
|
+
const openTuiRequested = shouldEnableOpenTuiFfi({
|
|
24
|
+
cli: 'flint',
|
|
25
|
+
args: process.argv.slice(2),
|
|
26
|
+
envRenderer: process.env.ORBH_TUI_RENDERER,
|
|
27
|
+
envNoChrome: process.env.ORBH_NO_CHROME,
|
|
28
|
+
stdinTty: Boolean(process.stdin.isTTY),
|
|
29
|
+
stdoutTty: Boolean(process.stdout.isTTY),
|
|
30
|
+
});
|
|
31
|
+
const [nodeMajor = 0, nodeMinor = 0] = process.versions.node.split('.').map(Number);
|
|
32
|
+
const ffiSupported = nodeMajor > 26 || (nodeMajor === 26 && nodeMinor >= 1);
|
|
33
|
+
const ffiFlag = '--experimental-ffi';
|
|
34
|
+
if (openTuiRequested && ffiSupported
|
|
35
|
+
&& !process.execArgv.includes(ffiFlag)
|
|
36
|
+
&& !process.env.NODE_OPTIONS?.split(/\s+/).includes(ffiFlag)
|
|
37
|
+
&& process.execve) {
|
|
38
|
+
const options = new Set(process.env.NODE_OPTIONS?.split(/\s+/).filter(Boolean) ?? []);
|
|
39
|
+
for (const option of [ffiFlag, '--disable-warning=ExperimentalWarning', '--disable-warning=DEP0205']) options.add(option);
|
|
40
|
+
process.execve(process.execPath, process.argv, {
|
|
41
|
+
...process.env,
|
|
42
|
+
NODE_OPTIONS: [...options].join(' '),
|
|
43
|
+
ORBH_TUI_FFI_REEXEC: '1',
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const srcEntry = join(sourcePackageRoot, 'src', 'index.ts');
|
|
48
|
+
const distEntry = join(runtimePackageRoot, 'dist', 'index.js');
|
|
49
|
+
|
|
50
|
+
// Default to production React/ink. Unset NODE_ENV loads react-reconciler's
|
|
51
|
+
// development build, whose per-render performance.measure() calls accumulate
|
|
52
|
+
// forever in Node's perf buffer — a ~1GB/hour leak in long-lived interactive
|
|
53
|
+
// views like `orbh list`. Explicit NODE_ENV=development still wins.
|
|
54
|
+
process.env.NODE_ENV ||= 'production';
|
|
55
|
+
|
|
56
|
+
// Performance: prefer the built dist and run it IN-PROCESS (one node process,
|
|
57
|
+
// no tsx, no recompile-on-every-invocation). Set FLINT_CLI_FORCE_SOURCE=1 to
|
|
58
|
+
// fall back to the checkout TypeScript source via tsx.
|
|
59
|
+
const forceSource =
|
|
60
|
+
process.env.FLINT_CLI_FORCE_SOURCE === '1' ||
|
|
61
|
+
process.env.FLINT_CLI_FORCE_SOURCE === 'true';
|
|
62
|
+
|
|
63
|
+
if (!forceSource && existsSync(distEntry)) {
|
|
64
|
+
process.env.FLINT_CLI_LAUNCHER = launcherPath;
|
|
65
|
+
process.env.FLINT_CLI_ENTRYPOINT = distEntry;
|
|
66
|
+
// The dist entry parses process.argv and runs the CLI on import. Awaiting
|
|
67
|
+
// keeps the launcher process alive for async work such as `flint orbh i`.
|
|
68
|
+
await import(distEntry);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const tsxPath = join(sourcePackageRoot, 'node_modules', '.bin', 'tsx');
|
|
73
|
+
|
|
74
|
+
// tsx still calls deprecated module.register(); Node 26 turned DEP0205 into a
|
|
75
|
+
// runtime warning. Disable it just for the source subprocess.
|
|
76
|
+
const suppressFlag = '--disable-warning=DEP0205';
|
|
77
|
+
const existingNodeOptions = process.env.NODE_OPTIONS ?? '';
|
|
78
|
+
const nodeOptions = existingNodeOptions.includes(suppressFlag)
|
|
79
|
+
? existingNodeOptions
|
|
80
|
+
: (existingNodeOptions ? `${existingNodeOptions} ${suppressFlag}` : suppressFlag);
|
|
81
|
+
|
|
82
|
+
const result = spawnSync(tsxPath, [srcEntry, ...process.argv.slice(2)], {
|
|
83
|
+
stdio: 'inherit',
|
|
84
|
+
env: {
|
|
85
|
+
...process.env,
|
|
86
|
+
NODE_OPTIONS: nodeOptions,
|
|
87
|
+
FLINT_CLI_LAUNCHER: launcherPath,
|
|
88
|
+
FLINT_CLI_ENTRYPOINT: srcEntry,
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
process.exit(result.status ?? 0);
|
|
93
|
+
}
|
package/bin/live-build-path.js
CHANGED
|
@@ -10,7 +10,8 @@ const hasCompleteDist = (packageRoot) =>
|
|
|
10
10
|
* inside a deployed/npm package has no checkout-level pointer and uses its own
|
|
11
11
|
* dist directory instead.
|
|
12
12
|
*/
|
|
13
|
-
export function resolveLiveBuildPackageRoot(sourcePackageRoot) {
|
|
13
|
+
export function resolveLiveBuildPackageRoot(sourcePackageRoot, { preferLiveBuild = true } = {}) {
|
|
14
|
+
if (!preferLiveBuild) return sourcePackageRoot;
|
|
14
15
|
const checkoutRoot = resolve(sourcePackageRoot, '..', '..');
|
|
15
16
|
const currentRelease = join(checkoutRoot, '.flint-live-build', 'current');
|
|
16
17
|
|
|
@@ -36,9 +36,9 @@ Orbh is a **meta-harness**: a session layer that launches, tracks, supervises, a
|
|
|
36
36
|
|
|
37
37
|
**Lifecycle.** `workState` is `working | needs-input | awaiting | finished | abandoned`. Headless/subagent turns end with `return --finish` (done; default) or `return --await` (dormant, expecting further interaction). Await still delivers that turn's result immediately — collectors resolve on the correlated result, not process state. An exit without return is re-prompted at most twice, then `failed-unreturned` and the session becomes awaiting (recoverable); `abandoned` is for kill/discard/operator verdicts, not a clean silent exit. Interactive sessions never declare their own activity: the launcher's PTY wrapper watches the harness title spinner and derives observed activity (busy/idle), which overrides declared state — spinner running reads as `working`, sitting at the prompt reads as `needs-input`.
|
|
38
38
|
|
|
39
|
-
**Delivery.**
|
|
39
|
+
**Delivery.** The machine-wide Orbh orchestrator sweep owns wake delivery for unattended (headless/subagent/peer) sessions across turns; `page arm` is an optional one-shot surface for mid-turn latency, and awaiting sessions wake on page-worthy events with a coalesced digest. Interactive sessions keep a run-scoped pager (`page arm` re-arm). Plain `park` is the legacy spelling of await; `message --wake` is unnecessary for awaiting targets (`--revive` still resumes an ended one). **Liveness notices**: when a counterparty you wait on (a `message request` target or a dispatched subagent) exits, fails to return, blocks on human input, or is killed, a typed NOTICES entry reaches you (digest, pager render, or Page) with a reason, trust grade, and guidance — follow it; `killed` means deliberately cancelled: do NOT re-send or spawn a replacement.
|
|
40
40
|
|
|
41
|
-
**The orchestrator.** A per-machine supervisor
|
|
41
|
+
**The orchestrator.** A per-machine supervisor runs the generalized awaiting-wake sweep, reaps un-returned turns, enforces job timeouts, and resumes awaiting sessions. You never manage it; it manages you.
|
|
42
42
|
|
|
43
43
|
**Surfaces.** Operators see sessions through `flint orbh list` / `inspect` / `watch`, terminal pane titles, and the NUU Orbit dashboard — all keyed on title and description. Your title is effectively a broadcast channel.
|
|
44
44
|
|
|
@@ -46,7 +46,7 @@ Orbh is a **meta-harness**: a session layer that launches, tracks, supervises, a
|
|
|
46
46
|
|
|
47
47
|
## Interactive self-compaction at 80% context
|
|
48
48
|
|
|
49
|
-
The Page `CONTEXT` line is the source of truth for context occupancy. At or above 80
|
|
49
|
+
The Page `CONTEXT` line is the source of truth for context occupancy. At or above 80% you write your own handoff — there is no distiller. Compaction is **three verbs**. (1) `flint orbh compact start` prints the handoff contract, the exact path in this session's `scratch/` to write it to, and your live Page (write OPEN OBLIGATIONS from that durable state, not from memory). It records no compaction intent and kills nothing — it holds the pager and marks this session **`[Compacting...]`** on every title surface (this pane's title, `orbh list`, the cockpit, Orbit) so the human watching knows the session is not doing their work right now. Write the handoff to that path with your own tools. (2) `flint orbh compact handoff` is the **turn-ending verb**: it validates the handoff while you are still alive, the pane manager ends this context immediately, and a fresh run of the **same session** relaunches in the same pane pointed at what you wrote. Do not plan work after it; there is no after. (3) `flint orbh compact finish` is run **by that fresh context**, not by you — once it has read the handoff and every path in its FILES list, it runs `finish` to clear `[Compacting...]` and release the pager hold — the normal release, so the marker honestly covers the successor's bootstrap too. A refusal at any step (a missing or malformed handoff, a dispatch claim whose child has not materialized) leaves your context alive — fix it and retry; materialized in-flight dispatches never refuse, they are detached and inherited by the successor as durable obligations. `flint orbh compact abort` releases the hold and clears the marker if you decide not to compact after all, and so does any other turn-ending verb — ending the turn before handoff releases them too.
|
|
50
50
|
|
|
51
51
|
## Register: title + description
|
|
52
52
|
|
|
@@ -84,7 +84,10 @@ As part of bootstrap, arm your session pager: run `flint orbh page arm` with you
|
|
|
84
84
|
|
|
85
85
|
Rooms are durable shared coordination channels with a message stream and a context library. If a manager tells you to join a room first, run `flint orbh room join <room>`, announce yourself with `flint orbh room post <room> "<text>"`, and read `flint orbh room read <room> --since-cursor`; check shared context with `flint orbh room context show <room>` before starting work. Use `room context append` or `room context edit --search "<old>" --replace "<new>"` only for deliberate shared-context updates.
|
|
86
86
|
|
|
87
|
+
<!-- Improvement intake disabled 2026-07-27: the improve loop is not working well enough to
|
|
88
|
+
advertise to every session. Restore this paragraph when the intake path is reliable again.
|
|
87
89
|
Any session may file harness bugs or Orbh improvement requests in the well-known `orbh-improvements` room (no join needed); start the envelope with `[improve] category=bug|improvement | reporter=<session-id> | title=<short title>`. Use `flint orbh improve "<description>" --title "<short title>"` as the convenience command.
|
|
90
|
+
-->
|
|
88
91
|
|
|
89
92
|
## Dispatching subagents
|
|
90
93
|
|
|
@@ -6,19 +6,27 @@ variables: {}
|
|
|
6
6
|
|
|
7
7
|
## Claude Code Orbh Behavior
|
|
8
8
|
|
|
9
|
-
Your Bash tool supports `run_in_background: true` — that is the native background execution the dispatch pattern asks for. Run blocking Orbh commands (`request -q`, `wait`, `job wait`) as background tasks: they survive foreground tool-call timeouts,
|
|
9
|
+
Your Bash tool supports `run_in_background: true` — that is the native background execution the dispatch pattern asks for. Run blocking Orbh commands (`request -q`, `wait`, `job wait`) as background tasks: they survive foreground tool-call timeouts, and while this turn is still running you are notified when they exit. Do not poll a background dispatch in a loop; continue other work and collect the output when the completion notification arrives.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
**A background task belongs to this run's process, not to your session.** Its completion notification can only re-invoke you inside the turn that started it. When the turn ends the task is killed with the process, and the next turn opens with an `__orphan_summary__ … stopped` notice where its output should have been. What it was waiting on — the child session, the job, the peer request — is durable and keeps going; only your local waiter dies. So a backgrounded wait is never a reason to end a turn. If you have nothing left to do in this turn, that is exactly the moment to `return --await`: the orchestrator then wakes you as a NEW turn when the thing lands, and it is the only mechanism that can.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
### Your last action in every turn is `return`
|
|
14
|
+
|
|
15
|
+
Every turn ends with a `flint orbh session return --finish` or `--await` Bash call — a fresh dispatch, a checkpoint continuation, and a ten-second wake turn that did nothing but read a digest, all alike. A closing assistant message is not a turn ending: Orbh never sees it, the run is recorded `exit-zero` with no result, and the bounded re-prompt that repairs it costs minutes of dead time. Before you write a status summary, check whether `return` has already run this turn; if it has not, that summary IS the return payload, so put it in the command instead of in the message.
|
|
16
|
+
|
|
17
|
+
The failure has one recognisable shape, and it is always the same sentence: "dispatched — waiting for the results", "pager re-armed, idling until the verdicts land". Whenever you catch yourself about to say you are waiting, waiting is the disposition — `return --await` and say it there.
|
|
18
|
+
|
|
19
|
+
### Arm the pager
|
|
20
|
+
|
|
21
|
+
For lowest-latency delivery during a live turn, run `flint orbh page arm` with `run_in_background: true`. It is an optional one-shot delivery surface that exits with a full Page render when a message, job completion, request, room event, or other page-worthy event arrives, and Claude surfaces that output as a background-task notification. Re-arm after delivery when low latency still matters. A missed re-arm only delays durable events until your next turn boundary. If it prints `session ended — pager exiting`, or you are about to return, do not arm again.
|
|
14
22
|
|
|
15
23
|
### Self-pacing in headless runs
|
|
16
24
|
|
|
17
|
-
In a headless/subagent run your process exits when the turn ends —
|
|
25
|
+
In a headless/subagent run your process exits when the turn ends — anything that would re-invoke THIS process later (ScheduleWakeup, /loop-style timers, an outstanding background-task completion) cannot fire after that exit and will strand the run as an un-returned turn. Being woken mid-turn by a background task earlier in the same turn is not evidence to the contrary: that notification only reached you because the turn was still alive. To pause until something happens, `return --await` (optionally `--until-group <g>`): the Orbh orchestrator sweep wakes the session with a digest as a NEW turn. Awaiting is the only headless self-pacing primitive.
|
|
18
26
|
|
|
19
27
|
### Talking to other sessions
|
|
20
28
|
|
|
21
|
-
Discover who is running with `flint orbh active` (titles, descriptions, phase/progress — write your own `register` description knowing peers read it there). `message send <id> "<text>"` is fire-and-forget; any message wakes an awaiting target, so `--wake` is unnecessary there, while `--revive` explicitly resumes an ended target. When you need an answer before proceeding, use a blocking peer request: run `flint orbh message request <id> "<question>"` with `run_in_background: true` and continue working — the answer arrives as a background-task notification. When your own Page shows a `↳ REQUEST` line, answer it promptly with the exact `flint orbh message respond <requestId> "<answer>"` command it displays (or cancel yours with `message request --cancel <requestId>`).
|
|
29
|
+
Discover who is running with `flint orbh active` (titles, descriptions, phase/progress — write your own `register` description knowing peers read it there). `message send <id> "<text>"` is fire-and-forget; any message wakes an awaiting target, so `--wake` is unnecessary there, while `--revive` explicitly resumes an ended target. When you need an answer before proceeding, use a blocking peer request: run `flint orbh message request <id> "<question>"` with `run_in_background: true` and continue working — the answer arrives as a background-task notification while this turn lasts, and if you run out of other work before it does, `return --await` rather than ending the turn on it. When your own Page shows a `↳ REQUEST` line, answer it promptly with the exact `flint orbh message respond <requestId> "<answer>"` command it displays (or cancel yours with `message request --cancel <requestId>`).
|
|
22
30
|
|
|
23
31
|
### Rooms
|
|
24
32
|
|
|
@@ -12,6 +12,6 @@ variables:
|
|
|
12
12
|
|
|
13
13
|
Use shell commands for the entire Orbh lifecycle. For blocking dispatches (`request -q`, `wait`), use your background execution facility if this environment provides one; otherwise dispatch through a detached Orbh job (`flint orbh job run --agent <runtime/profile> "<prompt>"`) and collect with `flint orbh job result <id>` — never let a blocking dispatch ride on a foreground shell call that can time out.
|
|
14
14
|
|
|
15
|
-
When low-latency mid-turn delivery matters, run `flint orbh page arm` with your background execution facility. It
|
|
15
|
+
When low-latency mid-turn delivery matters, run `flint orbh page arm` with your background execution facility. It is an optional one-shot delivery surface. Re-arm after it fires if low latency still matters; otherwise events arrive at the next turn boundary.
|
|
16
16
|
|
|
17
17
|
{{#if canContactHuman}}When you need human input while staying in the same turn, call `flint orbh session ask "<question>"` and use the command output as the answer. When your operator channel is asynchronous, call `flint orbh request "$ORBH_SESSION_ID" "<question>"`, then end the turn with `flint orbh session return --await "<status and pending question>"`. A later response wakes the awaiting session. Inspect it with `flint orbh requests "$ORBH_SESSION_ID"`, continue the work, and end that turn with an explicit return disposition.{{/if}}
|
|
@@ -6,4 +6,4 @@ variables: {}
|
|
|
6
6
|
|
|
7
7
|
## Droid Orbh Behavior
|
|
8
8
|
|
|
9
|
-
Run blocking Orbh collectors with Droid's background execution facility so foreground tool-call timeouts do not cancel them. For lowest-latency mid-turn delivery, run `flint orbh page arm` in the background as
|
|
9
|
+
Run blocking Orbh collectors with Droid's background execution facility so foreground tool-call timeouts do not cancel them. For lowest-latency mid-turn delivery, run `flint orbh page arm` in the background as an optional one-shot delivery surface. Re-arm after delivery when latency matters; otherwise events arrive at the next turn boundary.
|
|
@@ -6,4 +6,4 @@ variables: {}
|
|
|
6
6
|
|
|
7
7
|
## OpenCode Orbh Behavior
|
|
8
8
|
|
|
9
|
-
Run blocking Orbh collectors with OpenCode's background execution facility so foreground tool-call timeouts do not cancel them. For lowest-latency mid-turn delivery, run `flint orbh page arm` in the background as
|
|
9
|
+
Run blocking Orbh collectors with OpenCode's background execution facility so foreground tool-call timeouts do not cancel them. For lowest-latency mid-turn delivery, run `flint orbh page arm` in the background as an optional one-shot delivery surface. Re-arm after delivery when latency matters; otherwise events arrive at the next turn boundary.
|
|
@@ -39,19 +39,22 @@ Orbh is a meta-harness: a durable session layer that launches, tracks, supervise
|
|
|
39
39
|
|
|
40
40
|
**Modes.** Sessions run interactive `(I)`, headless `(H)` (you), or subagent `(S)`. A subagent has a collector waiting on its result. A peer is a bare headless launch with no collector and a manager-flavored prompt.
|
|
41
41
|
|
|
42
|
-
**Delivery.**
|
|
42
|
+
**Delivery.** The machine-wide Orbh orchestrator sweep owns unattended wake delivery across turns. While this run is live, `{{commandPath}} page arm` is an optional one-shot surface for the lowest-latency mid-run delivery; re-arm after delivery when latency matters. If you do not re-arm, events remain durable and arrive at your next turn boundary. When awaiting, the orchestrator resumes the session for any page-worthy event with a coalesced digest.
|
|
43
43
|
|
|
44
44
|
**Collection.** Waiting on a subagent means waiting on the result of the turn you dispatched. Its process state, awaits, resumes, and intermediate turns are not your concern. The collector unblocks when the correlated result exists or an explicit failure outcome occurs.
|
|
45
45
|
|
|
46
|
-
**Liveness notices.** When a counterparty you are waiting on — a pending `message request` target or a dispatched subagent — exits, fails to return, blocks on human input, or is killed, Orbh delivers a typed NOTICES entry to you (digest
|
|
46
|
+
**Liveness notices.** When a counterparty you are waiting on — a pending `message request` target or a dispatched subagent — exits, fails to return, blocks on human input, or is killed, Orbh delivers a typed NOTICES entry to you (digest or `{{commandPath}} page`) carrying a reason, a trust grade (`accurate`/`advisory`), and guidance. Follow the guidance instead of guessing at silence: `awaiting-input` means no nudge will help until a human acts; `killed` means the pending item was deliberately cancelled — do NOT re-send or spawn a replacement.
|
|
47
47
|
|
|
48
|
-
**The orchestrator.** A per-machine supervisor
|
|
48
|
+
**The orchestrator.** A per-machine supervisor runs the generalized awaiting-wake sweep, reaps un-returned turns, enforces job timeouts, and resumes awaiting sessions. You do not manage it.
|
|
49
49
|
|
|
50
50
|
**Surfaces.** Operators see sessions through `{{commandPath}} list`, `inspect`, `watch`, and dashboards. Keep title and description accurate. Your `return` payload is what your consumer reads.
|
|
51
51
|
|
|
52
52
|
Available coordination surfaces include `request`/`wait`/`result`, `active`, messages, blocking peer requests, rooms, background jobs and group barriers, the Page (`{{commandPath}} page`), human-input requests, profiles, and session bundles. Plain `park` is the legacy spelling of await; `message --wake` is unnecessary for awaiting targets.
|
|
53
53
|
|
|
54
|
+
<!-- Improvement intake disabled 2026-07-27: the improve loop is not working well enough to
|
|
55
|
+
advertise to every session. Restore this paragraph when the intake path is reliable again.
|
|
54
56
|
Any session may file harness bugs or Orbh improvement requests in the well-known `orbh-improvements` room (no join needed); start the envelope with `[improve] category=bug|improvement | reporter=<session-id> | title=<short title>`. Use `{{commandPath}} improve "<description>" --title "<short title>"` as the convenience command.
|
|
57
|
+
-->
|
|
55
58
|
|
|
56
59
|
## Register: title + description
|
|
57
60
|
|
|
@@ -73,6 +76,8 @@ Terminal output is not a deliverable. Return the full turn result as markdown:
|
|
|
73
76
|
Use `--finish` even for partial failure when no further interaction is expected; state what was done, what is missing, and why. Use `--await` only when the await-promise is real.
|
|
74
77
|
`return --await` still delivers this turn's result immediately, so any collector resolves now. Awaiting only keeps the session wakeable for a future turn, and every future turn ends with its own return.
|
|
75
78
|
|
|
79
|
+
This holds for short turns too. A wake turn that reads a digest, finds the work not ready, and stops on a message saying so has not paused — it has stranded itself, because nothing outside a live turn can re-invoke you except `--await`. Waiting is a disposition: return it.
|
|
80
|
+
|
|
76
81
|
## Dispatch subagents and peers
|
|
77
82
|
|
|
78
83
|
Dispatch collected work as a subagent:
|
|
@@ -91,12 +96,13 @@ Your session carries a free-form key/value interface: `{{commandPath}} session s
|
|
|
91
96
|
|
|
92
97
|
## Self-compaction at 80% context
|
|
93
98
|
|
|
94
|
-
Your context window fills across turns. The Page `CONTEXT` line — via `{{commandPath}} page` or a `page arm` delivery — is the source of truth for occupancy; do not invent alternate token accounting. An armed pager autofires a hard advisory when occupancy reaches 80%.
|
|
99
|
+
Your context window fills across turns. The Page `CONTEXT` line — via `{{commandPath}} page` or a `page arm` delivery — is the source of truth for occupancy; do not invent alternate token accounting. An armed pager autofires a hard advisory when occupancy reaches 80%. 80% is guidance, not a gate: nothing in the runtime fires these verbs for you, so treat it as the point by which you should have **started**.
|
|
95
100
|
|
|
96
|
-
|
|
101
|
+
At or above 80% (or clearly approaching it on a long turn, or whenever an operator asks) you write your own handoff — there is no distiller:
|
|
97
102
|
|
|
98
|
-
1.
|
|
99
|
-
2.
|
|
100
|
-
3.
|
|
103
|
+
1. `{{commandPath}} compact start` — prints the handoff contract, the exact path in this session's `scratch/` to write it to, and your live Page, so OPEN OBLIGATIONS come from durable state rather than memory. It records nothing and kills nothing; it holds the pager and marks the session `[Compacting...]`.
|
|
104
|
+
2. Write the handoff to that path with your own tools.
|
|
105
|
+
3. `{{commandPath}} compact handoff` — a **turn-ending verb**, sibling of `return`. It validates the handoff while you are still alive, ends this context, and relaunches a fresh run on the **same session id**: inbox, jobs, rooms, stations, scratch, and collector anchors all carry over. Do not plan work after it; there is no after.
|
|
106
|
+
4. `{{commandPath}} compact finish` — run by the **relaunched context**, once it has read the handoff and every path in its FILES list directly (the summary is orientation, not ground truth). It clears `[Compacting...]` and releases the pager hold.
|
|
101
107
|
|
|
102
|
-
|
|
108
|
+
Every refusal — a missing or malformed handoff, a dispatch claim whose child has not materialized — leaves your context alive: fix it and retry. Materialized in-flight dispatches do not refuse; they are detached and inherited by the successor as durable obligations. `{{commandPath}} compact abort` backs out before handoff, and any other turn-ending verb releases the hold too.
|
|
@@ -40,7 +40,7 @@ Your run is one **turn**. Every turn ends with an explicit return disposition:
|
|
|
40
40
|
|
|
41
41
|
Await is a promise of further interaction. As a peer, default to `--await` while the standing duty, shared coordination, or expected follow-ups remain live. Finish when no further interaction is expected. Exiting without returning gets the turn re-prompted at most twice; after the second failure it becomes `failed-unreturned` and the session becomes awaiting.
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
The machine-wide Orbh orchestrator sweep owns unattended wake delivery across turns. During a live run, `{{commandPath}} page arm` is an optional one-shot surface for lowest-latency mid-run delivery. Re-arm after delivery when latency matters; otherwise durable events arrive at your next turn boundary. While awaiting, the orchestrator resumes you for any page-worthy event with a coalesced digest.
|
|
44
44
|
|
|
45
45
|
If a counterparty you wait on (a `message request` target or a dispatched subagent) exits, fails to return, blocks on human input, or is killed, a typed NOTICES entry reaches you with a reason, trust grade, and guidance — follow it; `killed` means deliberately cancelled: do NOT re-send or spawn a replacement.
|
|
46
46
|
|
|
@@ -51,7 +51,10 @@ If a counterparty you wait on (a `message request` target or a dispatched subage
|
|
|
51
51
|
|
|
52
52
|
There is no collector for you. Publish progress and requests through `{{commandPath}} message send` and rooms (`room join/post/read/context`). Read the Page at meaningful seams. Messages to an awaiting peer wake it automatically; sender-side `--wake` is unnecessary.
|
|
53
53
|
|
|
54
|
+
<!-- Improvement intake disabled 2026-07-27: the improve loop is not working well enough to
|
|
55
|
+
advertise to every session. Restore this paragraph when the intake path is reliable again.
|
|
54
56
|
Any session may file harness bugs or Orbh improvement requests in the well-known `orbh-improvements` room (no join needed); start the envelope with `[improve] category=bug|improvement | reporter=<session-id> | title=<short title>`. Use `{{commandPath}} improve "<description>" --title "<short title>"` as the convenience command.
|
|
57
|
+
-->
|
|
55
58
|
|
|
56
59
|
You may dispatch collected subagents with `{{commandPath}} request -q <runtime/profile> '<complete prompt>'`. Run the blocking collector with your harness's native background execution. Waiting means waiting on the subagent's correlated result, not its process state. Subagents may recurse; depth and fan-out are capped.
|
|
57
60
|
|
|
@@ -61,4 +64,4 @@ Your session also exposes `{{commandPath}} session set/get` for workspace-define
|
|
|
61
64
|
|
|
62
65
|
## Self-compaction at 80% context
|
|
63
66
|
|
|
64
|
-
The Page `CONTEXT` line — via `{{commandPath}} page` or a `page arm` delivery — is the source of truth for context occupancy; an armed pager autofires a hard advisory at 80%.
|
|
67
|
+
The Page `CONTEXT` line — via `{{commandPath}} page` or a `page arm` delivery — is the source of truth for context occupancy; an armed pager autofires a hard advisory at 80%. 80% is guidance, not a gate: nothing fires on your behalf, so treat it as the point by which you should have **started**. At or above 80% (or clearly approaching it on a long turn) you write your own handoff — there is no distiller. Run `{{commandPath}} compact start` (it prints the handoff contract, the exact path in your spool's `scratch/` to write it to, and your live Page, so the standing duty and OPEN OBLIGATIONS come from durable state rather than memory), write the handoff to that path with your own tools, then run `{{commandPath}} compact handoff` — a **turn-ending verb**, sibling of `return`. It validates the handoff while you are still alive, ends this context, and relaunches a fresh run on the **same session id**, so your inbox, jobs, rooms, and station bindings carry over. Do not plan work after it; there is no after. Every refusal leaves your context alive — fix it and retry; materialized in-flight dispatches are detached and inherited by the successor rather than refused, and `{{commandPath}} compact abort` backs out. If you wake as the relaunched context, read the handoff and every path in its FILES list directly before acting — the summary is orientation, not ground truth — then run `{{commandPath}} compact finish` and resume the standing duty.
|
|
@@ -41,7 +41,7 @@ Orbh is a meta-harness: a durable session layer that launches, tracks, supervise
|
|
|
41
41
|
|
|
42
42
|
A session is an event-sourced Orb spool with a stable id, title and description, a free-form key/value interface (`session set/get`), and runs. Your run is one **turn**. Every turn ends with `return --finish` or `return --await`; `--finish` is the default. Await is a promise of further interaction. `return --await` still delivers this turn's result immediately, so your collector resolves now; awaiting only keeps you wakeable for a future turn, which ends with its own return. As a subagent, use `--await` only when your dispatcher granted or requested follow-up availability. Exiting without returning gets this turn re-prompted at most twice, then marks it `failed-unreturned` and leaves the session awaiting.
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
The machine-wide Orbh orchestrator sweep owns unattended wake delivery across turns. During a live run, `{{commandPath}} page arm` is an optional one-shot surface for lowest-latency mid-run delivery. Re-arm after delivery when latency matters; otherwise durable events arrive at your next turn boundary. While awaiting, the orchestrator resumes the session for any page-worthy event with a coalesced digest.
|
|
45
45
|
|
|
46
46
|
If a counterparty you wait on (a `message request` target or a dispatched subagent) exits, fails to return, blocks on human input, or is killed, a typed NOTICES entry reaches you with a reason, trust grade, and guidance — follow it; `killed` means deliberately cancelled: do NOT re-send or spawn a replacement.
|
|
47
47
|
|
|
@@ -68,11 +68,14 @@ Run blocking collection with your harness's native background execution. Waiting
|
|
|
68
68
|
|
|
69
69
|
For work that should outlive you or belongs to no one, use bare `launch` to create a **peer**. A peer is not your subagent and no collector waits for it. Coordinate by message or room.
|
|
70
70
|
|
|
71
|
+
<!-- Improvement intake disabled 2026-07-27: the improve loop is not working well enough to
|
|
72
|
+
advertise to every session. Restore this paragraph when the intake path is reliable again.
|
|
71
73
|
Any session may file harness bugs or Orbh improvement requests in the well-known `orbh-improvements` room (no join needed); start the envelope with `[improve] category=bug|improvement | reporter=<session-id> | title=<short title>`. Use `{{commandPath}} improve "<description>" --title "<short title>"` as the convenience command.
|
|
74
|
+
-->
|
|
72
75
|
|
|
73
76
|
## Self-compaction at 80% context
|
|
74
77
|
|
|
75
|
-
The Page `CONTEXT` line — via `{{commandPath}} page` or a `page arm` delivery — is the source of truth for context occupancy; an armed pager autofires a hard advisory at 80%.
|
|
78
|
+
The Page `CONTEXT` line — via `{{commandPath}} page` or a `page arm` delivery — is the source of truth for context occupancy; an armed pager autofires a hard advisory at 80%. 80% is guidance, not a gate: nothing fires on your behalf, so treat it as the point by which you should have **started**. At or above 80% (or clearly approaching it on a long turn) you write your own handoff — there is no distiller. Run `{{commandPath}} compact start` (it prints the handoff contract, the exact path in your spool's `scratch/` to write it to, and your live Page, so OPEN OBLIGATIONS come from durable state rather than memory), write the handoff to that path with your own tools, then run `{{commandPath}} compact handoff` — a **turn-ending verb**, sibling of `return`. It validates the handoff while you are still alive, ends this context, and relaunches a fresh run on the **same session id**, so your dispatcher's collector anchor, inbox, and jobs carry over. Do not plan work after it; there is no after. Every refusal leaves your context alive — fix it and retry; materialized in-flight dispatches are detached and inherited by the successor rather than refused, and `{{commandPath}} compact abort` backs out. If you wake as the relaunched context, read the handoff and every path in its FILES list directly before acting — the summary is orientation, not ground truth — then run `{{commandPath}} compact finish`.
|
|
76
79
|
|
|
77
80
|
## End this turn
|
|
78
81
|
|
|
@@ -81,3 +84,5 @@ The Page `CONTEXT` line — via `{{commandPath}} page` or a `page arm` delivery
|
|
|
81
84
|
```
|
|
82
85
|
|
|
83
86
|
Use `--await` instead only when the dispatcher granted or requested follow-up availability. Do not `close` or `end`; return owns the disposition.
|
|
87
|
+
|
|
88
|
+
This is the last action of every turn, short ones included: a wake turn that reads a digest and stops on a message saying it is still waiting has stranded itself, because nothing outside a live turn can re-invoke you except an `--await` return.
|