@4fu/arbor-environment-dir 0.0.0-stage → 2026.1106.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 4fuu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,188 @@
1
- # Temporary Holding Version
1
+ # arbor
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ arbor is a framework for an agent without sessions: many context windows run
4
+ at once over one shared record, so work moves in parallel, replies come fast,
5
+ and every window can find everything that was ever done.
6
+
7
+ A deployment composes the pieces it wants and registers plugins for the domains
8
+ it serves. The framework guarantees what an agent cannot guarantee for itself -
9
+ the record, isolation, exactly-once effects, the checks, dispatch - and leaves
10
+ everything that takes judgment, such as git, merges and conflicts, to the agent.
11
+
12
+ ## Core ideas
13
+
14
+ - **No sessions to manage.** Work runs in disposable context windows. Any
15
+ number run at the same time; none of them owns anything, and a window that
16
+ ends loses nothing. The model looks up what it needs instead of carrying it.
17
+ - **One record, shared live.** Every input, tool call, result, send and note
18
+ revision goes into one journal and is visible to every window as
19
+ soon as it is written. Records never merge and are never lost.
20
+ - **Dispatch by a judge.** A judge (Jev by default, replaceable) decides which
21
+ live window takes a new input, or opens a new one. There is no router session
22
+ and no main line.
23
+ - **Current without locks.** Windows run in parallel. New events on a
24
+ conversation a window is in are steered into it as they arrive; only a note
25
+ write is checked against the revision the window read.
26
+ - **Mechanisms in the framework, policy in the agent.** The framework
27
+ guarantees what an agent cannot guarantee for itself - the record, isolation,
28
+ exactly-once effects, the checks, dispatch. Everything that takes judgment,
29
+ such as git, merges and conflicts, is the agent's.
30
+ - **A directory per window.** Each window gets its own directory, and a
31
+ continuation keeps working in its predecessor's directory in place. It is not
32
+ bound to a repository; the agent clones and pushes what it needs.
33
+ - **A self-changing environment.** The model adds and changes skills, MCP
34
+ servers and resident services itself; a `reload` validates the change and
35
+ broadcasts it to every live window as a delta of names and descriptions.
36
+ - **Minimal limits.** The framework sets no policy. Permissions, privacy
37
+ review, budgets and approvals belong to deployments built on it, through
38
+ hooks.
39
+ - **Direct tools plus code mode.** Decisions are direct tools; everything else
40
+ is a module called from `code_exec`. There is no CLI.
41
+ - **Every module is replaceable.** The agent backend (pi by default), the
42
+ journal, notes, workspaces, the judge, channels and the rest are interfaces
43
+ ([docs/architecture.md](docs/architecture.md#interfaces)).
44
+
45
+ ## Install
46
+
47
+ Node 24 or newer. Every package is published separately, so a deployment
48
+ installs the core and the defaults it wants:
49
+
50
+ ```sh
51
+ npm install @4fu/arbor-core @4fu/arbor-journal-sqlite @4fu/arbor-notes-sqlite \
52
+ @4fu/arbor-environment-dir @4fu/arbor-supervisor-process @4fu/arbor-agent-pi \
53
+ @4fu/arbor-workspace-dir
54
+ ```
55
+
56
+ `@4fu/arbor-plugin-http` is optional; it is the example plugin below.
57
+ `@4fu/arbor-segmenter-jieba` is an optional peer of the journal.
58
+
59
+ ## A minimal host
60
+
61
+ The smallest host needs a journal and a backend. Adding the defaults and one
62
+ plugin is what a deployment usually starts from:
63
+
64
+ ```js
65
+ import { mkdtemp } from "node:fs/promises";
66
+ import { tmpdir } from "node:os";
67
+ import { join } from "node:path";
68
+ import { Arbor } from "@4fu/arbor-core";
69
+ import { DirectoryEnvironment } from "@4fu/arbor-environment-dir";
70
+ import { SqliteJournal } from "@4fu/arbor-journal-sqlite";
71
+ import { SqliteNoteStore } from "@4fu/arbor-notes-sqlite";
72
+ import { ProcessSupervisor } from "@4fu/arbor-supervisor-process";
73
+ import { DirectoryWorkspace } from "@4fu/arbor-workspace-dir";
74
+ import { PiAgentBackend } from "@4fu/arbor-agent-pi";
75
+ import { HttpChannel, httpPlugin } from "@4fu/arbor-plugin-http";
76
+
77
+ const root = await mkdtemp(join(tmpdir(), "arbor-"));
78
+
79
+ // Every module below is a default implementation of an interface. Replace any
80
+ // one by passing your own; the host does not change.
81
+ const journal = new SqliteJournal({ path: join(root, "journal.sqlite") });
82
+ const notes = new SqliteNoteStore({ path: join(root, "notes.sqlite"), journal });
83
+ const supervisor = new ProcessSupervisor({ root: join(root, "services") });
84
+ const environment = new DirectoryEnvironment({ root: join(root, "env"), supervisor });
85
+ const workspace = new DirectoryWorkspace({ root: join(root, "workspaces") });
86
+
87
+ // A plugin adds; it never patches the core. This one is a chat channel: a
88
+ // webhook in, a window's sends out, and a page that drives the whole host.
89
+ let arbor;
90
+ const channel = new HttpChannel({ host: "127.0.0.1", port: 8787, board: () => arbor.boardLines() });
91
+ arbor = new Arbor({
92
+ journal,
93
+ notes,
94
+ backend: new PiAgentBackend(),
95
+ workspace,
96
+ environment,
97
+ supervisor,
98
+ plugins: [httpPlugin({ channel })],
99
+ });
100
+
101
+ await arbor.start();
102
+ await channel.listen();
103
+ ```
104
+
105
+ `arbor.open(task)` dispatches one task, and the journal is where everything that
106
+ happened can be read back ([docs/journal.md](docs/journal.md)). The workspace a
107
+ window gets, and how a continuation keeps working in it, are in
108
+ [docs/workspaces.md](docs/workspaces.md).
109
+
110
+ ## Packages
111
+
112
+ | Package | What it is |
113
+ | --- | --- |
114
+ | [`@4fu/arbor-interfaces`](packages/interfaces) | every replaceable interface, plus the core's own shapes. Types only. |
115
+ | [`@4fu/arbor-errors`](packages/errors) | the error shape every refusal uses: the rule and the next action |
116
+ | [`@4fu/arbor-prompt`](packages/prompt) | typed blocks, the one deterministic renderer and the replaceable text table |
117
+ | [`@4fu/arbor-tools`](packages/tools) | the entry points: direct tools, code mode modules, the host API |
118
+ | [`@4fu/arbor-journal-sqlite`](packages/journal-sqlite) | the default `Journal` and `BlobStore` on SQLite |
119
+ | [`@4fu/arbor-notes-sqlite`](packages/notes-sqlite) | the default `NoteStore` on SQLite, beside the journal |
120
+ | [`@4fu/arbor-segmenter-jieba`](packages/segmenter-jieba) | the default `Segmenter`: jieba's dictionary segmentation for the word index, optional |
121
+ | [`@4fu/arbor-workspace-dir`](packages/workspace-dir) | the default `Workspace`: one directory per window, with a continuation working in its predecessor's directory in place |
122
+ | [`@4fu/arbor-environment-dir`](packages/environment-dir) | the default `Environment`: one `/env` directory, a revision per item, validation and deltas |
123
+ | [`@4fu/arbor-supervisor-process`](packages/supervisor-process) | the default `Supervisor`: one host process per service, restart with backoff, logs |
124
+ | [`@4fu/arbor-judge-jev`](packages/judge-jev) | the default `Judge`: Jev, TypeSafe AI's System One classifier, answering the dispatch decisions |
125
+ | [`@4fu/arbor-agent-pi`](packages/agent-pi) | the default `AgentBackend`: pi-agent-core in process |
126
+ | [`@4fu/arbor-plugin-http`](packages/plugin-http) | the HTTP test channel: a webhook in, sends out, a page that drives the whole host |
127
+ | [`@4fu/arbor-core`](packages/core) | the service layer: one implementation of every operation - the window lifecycle, dispatch, coordination, the board, notes access, workspaces, effects and schedules |
128
+
129
+ ### Defaults are examples
130
+
131
+ Every default above is a reference implementation of its interface, written to
132
+ be read and replaced. A real deployment is expected to swap out the ones that do
133
+ not fit: the SQLite journal and notes, the directory workspace, the process
134
+ supervisor, the environment directory, the HTTP test plugin and the Jev judge
135
+ are all examples of what an interface can be satisfied by, not commitments.
136
+ What a deployment keeps is its own choice, and the interfaces are the contract
137
+ ([docs/architecture.md](docs/architecture.md#interfaces)).
138
+
139
+ Each package's own documentation holds the limits of that default, so a
140
+ deployment can see what it would be taking on before it adopts one.
141
+
142
+ ## Documents
143
+
144
+ | Page | Contents |
145
+ | --- | --- |
146
+ | [docs/overview.md](docs/overview.md) | goals, concepts, principles, routes considered |
147
+ | [docs/architecture.md](docs/architecture.md) | layers, interfaces, stack, models, plugins, hooks, data placement |
148
+ | [docs/journal.md](docs/journal.md) | events and their order, targets, watermarks, external ids, refs, tracing, media, redaction |
149
+ | [docs/windows.md](docs/windows.md) | window lifecycle, prompt layout, reminders, continuation |
150
+ | [docs/dispatch.md](docs/dispatch.md) | task sources, judge decisions, insertion timing, backpressure, scheduled tasks |
151
+ | [docs/coordination.md](docs/coordination.md) | the board, expectations, deliveries and broadcasts, staying current, effects |
152
+ | [docs/environment.md](docs/environment.md) | skills, MCP servers and services: using, changing, reload, built-in skills, deltas |
153
+ | [docs/memory.md](docs/memory.md) | notes, the active set, anchors, links |
154
+ | [docs/workspaces.md](docs/workspaces.md) | per-window directories: isolation, continuity, what the agent does |
155
+ | [docs/tools.md](docs/tools.md) | direct tools and code mode modules |
156
+ | [docs/prompting.md](docs/prompting.md) | how every model-facing text is written: layers, notes, deliveries, errors, judge questions, terms |
157
+ | [docs/release.md](docs/release.md) | calendar versioning, the manual release steps, how a package is published |
158
+
159
+ ## Repository layout
160
+
161
+ `pnpm check` typechecks and tests every package on its TypeScript sources.
162
+ `pnpm build` emits the `dist` publishing needs, and `pnpm pack` shows what each
163
+ tarball would contain ([docs/release.md](docs/release.md)).
164
+
165
+ | Path | Layer |
166
+ | --- | --- |
167
+ | `packages/interfaces` | the interfaces layer: every replaceable interface, plus the core's own shapes. Types only. |
168
+ | `packages/errors` | the error shape every refusal uses: the rule and the next action |
169
+ | `packages/journal-sqlite` | the default `Journal` and `BlobStore` on SQLite |
170
+ | `packages/segmenter-jieba` | the default `Segmenter`: jieba's dictionary segmentation for the word index, optional |
171
+ | `packages/notes-sqlite` | the default `NoteStore` on SQLite, beside the journal |
172
+ | `packages/judge-jev` | the default `Judge`: Jev, TypeSafe AI's System One classifier, answering the dispatch decisions |
173
+ | `packages/workspace-dir` | the default `Workspace`: one directory per window, with a continuation working in its predecessor's directory in place, and the lookup of another window's directory it contributes to the catalog |
174
+ | `packages/environment-dir` | the default `Environment`: one `/env` directory, a revision per item, validation and deltas |
175
+ | `packages/supervisor-process` | the default `Supervisor`: one host process per service, restart with backoff, logs |
176
+ | `packages/plugin-http` | the HTTP test channel: a webhook in, sends out, a page that drives the whole host |
177
+ | `packages/prompt` | typed blocks, the one deterministic renderer and the replaceable text table |
178
+ | `packages/agent-pi` | the default `AgentBackend`: pi-agent-core in process |
179
+ | `packages/tools` | the entry points: direct tools, code mode modules, the host API |
180
+ | `packages/core` | the service layer: one implementation of every operation - the window lifecycle, dispatch, coordination, the board, notes access, workspaces, effects, schedules and the snapshot epochs |
181
+
182
+ `mise.toml` pins the toolchain - Node and pnpm - installed under
183
+ `~/.local/share/mise`, and `.agents/setup` prepares a machine from it. A machine
184
+ without mise is expected to carry Node 24 or newer and pnpm 12 already.
185
+
186
+ ## License
187
+
188
+ MIT, see [LICENSE](LICENSE).
@@ -0,0 +1,111 @@
1
+ /**
2
+ * The default `Environment`: one shared directory, mounted read-only into every
3
+ * window at `/env` (docs/environment.md).
4
+ *
5
+ * ```text
6
+ * <root>/env/skills/<name>/ a skill, with its scripts beside it
7
+ * <root>/env/mcp/<name>.json an MCP server's configuration
8
+ * <root>/env/mcp/<name>/<name>.json an MCP server that brought its own code
9
+ * <root>/env/services/<name>/svc.json a service, beside its files
10
+ * <root>/env/settings.json identity, rules, judge, model tiers
11
+ * <root>/revisions/<kind>/<name>/<n>/ one copy of every installed revision
12
+ * <root>/index.json the items and their revisions
13
+ * ```
14
+ *
15
+ * Each item has its own revision, so two windows changing different items never
16
+ * conflict. A draft is staged and validated before anything is replaced, so a
17
+ * refused `reload` changes nothing: an item is replaced as a whole, and a
18
+ * service that fails its health check is rolled back to the revision it had.
19
+ *
20
+ * The built-in skills ship with the framework and are seeded on first use. They
21
+ * are read-only: the model writes its own skills beside them.
22
+ */
23
+ import type { EnvDelta, EnvDraft, EnvItem, EnvItemKind, EnvItemName, EnvItemRef, EnvRef, EnvRevision, EnvValidation, Environment, Settings, Supervisor } from "@4fu/arbor-interfaces";
24
+ /** The built-in skills, seeded read-only on first use. */
25
+ export declare const BUILTIN_SKILLS: readonly ["arbor-skill", "arbor-mcp", "arbor-service", "arbor-settings"];
26
+ /** The built-in skill that covers each kind, which every refusal names. */
27
+ export declare const BUILTIN_SKILL_PATH: Record<EnvItemKind, string>;
28
+ export interface DirectoryEnvironmentOptions {
29
+ /** The directory the environment lives in. */
30
+ root: string;
31
+ /** What a window reads the environment at. Defaults to `<root>/env`. */
32
+ mount?: string;
33
+ /** The service supervisor. Absent when no service may run. */
34
+ supervisor?: Supervisor;
35
+ /** Where a secret's value comes from. A configuration names one, never holds it. */
36
+ secretOf?: (name: string) => string | undefined;
37
+ /** The clock. Tests pass a fixed one. */
38
+ now?: () => number;
39
+ }
40
+ export declare class DirectoryEnvironment implements Environment {
41
+ /** What a window reads the environment at. */
42
+ readonly mount: string;
43
+ /** The directory the environment lives in, which the host reads. */
44
+ readonly directory: string;
45
+ private readonly root;
46
+ private readonly supervisor;
47
+ private readonly secretOf;
48
+ private readonly clock;
49
+ private index;
50
+ /** Every environment revision, oldest first. */
51
+ private log;
52
+ private ready;
53
+ constructor(options: DirectoryEnvironmentOptions);
54
+ revision(): Promise<EnvRef>;
55
+ list(): Promise<readonly EnvItem[]>;
56
+ history(item?: EnvItemRef | EnvItemName): Promise<readonly EnvRevision[]>;
57
+ settings(): Promise<Settings | undefined>;
58
+ validate(drafts: readonly EnvDraft[]): Promise<EnvValidation>;
59
+ install(drafts: readonly EnvDraft[], remove?: readonly (EnvItemRef | EnvItemName)[]): Promise<EnvRevision>;
60
+ remove(items: readonly (EnvItemRef | EnvItemName)[]): Promise<EnvRevision>;
61
+ delta(since: EnvRef): Promise<EnvDelta>;
62
+ /** Start every installed service again, after a host restart. */
63
+ resume(): Promise<void>;
64
+ call(server: string, fn: string, args: Record<string, unknown>): Promise<string>;
65
+ /** Start one MCP server for one call. */
66
+ private serve;
67
+ /** Copy every draft into the staging area, so nothing is read in place. */
68
+ private stage;
69
+ /** Validate every staged draft, or refuse with the first one that failed. */
70
+ private checkAll;
71
+ /** One staged draft: its description, and what its kind needs beyond that. */
72
+ private check;
73
+ /** A skill parses: a title, then one line that says what it is for. */
74
+ private checkSkill;
75
+ /** The settings parse into the settings shape. */
76
+ private checkSettings;
77
+ /**
78
+ * An MCP server starts and lists its functions. It runs from the staged
79
+ * directory, so a draft that brought its own code is validated exactly as it
80
+ * will run once installed.
81
+ */
82
+ private checkMcp;
83
+ /** A service's configuration parses, and it is started when it installs. */
84
+ private checkService;
85
+ /**
86
+ * Replace one item as a whole, then start a service and wait for its health
87
+ * check. A service that never becomes healthy is rolled back to the revision
88
+ * it had, so a refused `reload` changes nothing.
89
+ */
90
+ private apply;
91
+ /** Put the revision an item had back, after a failed install. */
92
+ private rollback;
93
+ /** Stop a service and remove its files. */
94
+ private removeItem;
95
+ /** Keep one copy of an installed revision, so a revert can install it again. */
96
+ private keepRevision;
97
+ /**
98
+ * The revision check (docs/environment.md#changing-the-environment): a draft
99
+ * is based on the revision its window read, and another window that installed
100
+ * a newer one since refuses the call, so the model merges and calls again.
101
+ */
102
+ private checkRevisions;
103
+ /** The items a `reload({ remove })` names, or a refusal naming what is missing. */
104
+ private leaving;
105
+ /** The index, seeded with the built-in skills on first use. */
106
+ private read;
107
+ private load;
108
+ /** Copy the built-in skills in, read-only, when they are not there yet. */
109
+ private seedBuiltins;
110
+ private write;
111
+ }