Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pnpm workspace, Node >= 22.19, ESM only, TypeScript.
| `docs/toolchain.md` | Build, CI, release — read before changing `.github/`, `scripts/`, root configs |
| `examples/site` | Runnable example |
| `demos/` | One dated report per merged change — regenerate on the branch, then leave it |
| `FOLLOW_WORK.md` | Work a branch decided not to do, and why it is worth doing |
| `planning/` | Forward-looking tracking: `backlog.md`, `changelog.md` |

## Thesis

Expand All @@ -49,7 +49,7 @@ same kind, and every one enters as a message. Keep both framings in

`README.md` and `docs/` document what is implemented. Do not write about
unbuilt concepts (channels, deployment targets) there; deferred work goes in
`FOLLOW_WORK.md`.
`planning/backlog.md`.

## Commands

Expand Down
2 changes: 1 addition & 1 deletion demos/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ is imperative. The second run read every record as a week stale, because the
runtime stamps the real date into every context and the scenario is set on
25 August; the goal now names the site's calendar. One artefact remains: a
`2>/dev/null` created `dev/null` on the drive, because just-bash treats that
path as a plain file. `FOLLOW_WORK.md` holds it.
path as a plain file. `planning/backlog.md` holds it.

**One Assistant, Three Readers.** The run that moved the assistant from the
person to the room. `startSession` now takes `assistant` as a required
Expand Down
2 changes: 1 addition & 1 deletion docs/exchange.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ again, and that question opens a new exchange.
A closed exchange is an owner and a range, so it is derivable from the
record. Nothing derives it today; a host that wants a history of exchanges
records the `exchange_closed` events as they arrive.
[`FOLLOW_WORK.md`](../FOLLOW_WORK.md) holds the work.
[`planning/backlog.md`](../planning/backlog.md) holds the work.

---

Expand Down
10 changes: 4 additions & 6 deletions docs/workspace.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ credential. A workspace provisions one for it, inside `connect` (§7).
leaves the workspace — an external API, a secret, another service — needs
a separate mechanism: a credentials provider paired with a sidecar proxy,
injecting a credential or issuing a short-lived token per call.
[`FOLLOW_WORK.md`](../FOLLOW_WORK.md) tracks that boundary. This document
leaves it out.
[`planning/backlog.md`](../planning/backlog.md) tracks that boundary. This
document leaves it out.

**A workspace is a container of persistent entities, each of a different
kind.** The filesystem and shell (§8) are one kind. Reminders and tasks
Expand Down Expand Up @@ -296,8 +296,6 @@ engine with its own lanes, compaction, and navigation. Ambion's runtime
imports Pi's lower-level `Agent` class (`activation.ts`, `session.ts`), and
`AgentHarness` appears in no file under `packages/ambion/src`. `ToolContext`
is addressed to `defineTool`'s own shape, at the layer Ambion builds on.
`FOLLOW_WORK.md` holds the wider question of which class is Ambion's
foundation.

---

Expand Down Expand Up @@ -563,8 +561,8 @@ empty `/dev/null` into a fresh `InMemoryFs` and treats it as a file: a
command that redirects into it appends to it (probed: `echo hi >/dev/null`
leaves three bytes there). `ReadWriteFs` starts without `/dev`, so the same
redirect creates `dev/null` under the root, on disk. A directory backend
therefore holds whatever agents discarded, and [`FOLLOW_WORK.md`](../FOLLOW_WORK.md)
tracks it.
therefore holds whatever agents discarded, and
[`planning/backlog.md`](../planning/backlog.md) tracks it.

**`env.cwd` is the agent's home for the life of the `env`, and nothing
tracks it further.** Pi's `FileSystem` declares `cwd` as a plain property,
Expand Down
2 changes: 1 addition & 1 deletion packages/ambion/src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,7 @@ class SessionImpl implements Session {
// The assistant's hands are the runtime's, and it holds them only for the activation
// it was woken for. Nothing wakes the assistant today but the close of an
// exchange; when something else does — a wider attention, per
// FOLLOW_WORK.md — it must arrive with empty hands until somebody adds a
// planning/backlog.md — it must arrive with empty hands until somebody adds a
// `say` here on purpose. assistant.md §12 makes that a deliberate decision.
if (this.assistant.is(seat.def.name)) {
const closing = this.assistant.closing();
Expand Down
111 changes: 34 additions & 77 deletions FOLLOW_WORK.md → planning/backlog.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# Follow-up work
# Backlog

Work this branch decided not to do, and why it is worth doing. Each item says
Work a branch decided not to do, and why it is worth doing. Each item says
what it is, what it costs, and where the reasoning already lives. The design
contracts in [`docs/`](docs) hold the open questions about a design; this file
holds the work.
contracts in [`docs/`](../docs) hold the open questions about a design; this
file holds the work. [`changelog.md`](changelog.md) holds what shipped.

## Steering an exchange from what a person holds

Expand All @@ -15,7 +15,7 @@ reads what a person holds and what a message is, so the room can run it as
a check, and it costs no activation.

**Why.** The preferences are in one place already: the `preferences` field on
`defineHuman` ([`docs/assistant.md`](docs/assistant.md) §2), which today
`defineHuman` ([`docs/assistant.md`](../docs/assistant.md) §2), which today
reaches the assistant alone, at the close. A person who has said what they act
on has said something the room could use while it is still deciding what to
say. It is also the cheapest of the three rungs in §12: a check costs
Expand All @@ -41,9 +41,9 @@ nothing, and rung 3 pays for an activation.
(`SUMMARY_PARAGRAPH`) is the precedent.

**Where.** `dispatch` and `handsFor` in
[`session.ts`](packages/ambion/src/session.ts), `wakes` in
[`seat.ts`](packages/ambion/src/seat.ts), the assistant's paragraphs in
[`render.ts`](packages/ambion/src/render.ts).
[`session.ts`](../packages/ambion/src/session.ts), `wakes` in
[`seat.ts`](../packages/ambion/src/seat.ts), the assistant's paragraphs in
[`render.ts`](../packages/ambion/src/render.ts).

## Reseating: attention that a running room can change

Expand All @@ -52,12 +52,12 @@ moves. `session.reseat(name, attention)` would let a host widen or narrow one
while the room runs.

**Why.** Attention is now the whole of what a seating decides
([`docs/agent.md`](docs/agent.md) rule 6): one widening scale, and one
([`docs/agent.md`](../docs/agent.md) rule 6): one widening scale, and one
comparison against a message's reach decides who wakes. Everything that reads
it already reads it per activation, so a seat that changes point costs nothing
to route.

It is also what [`docs/assistant.md`](docs/assistant.md) §12's rung 3 wants. The assistant is
It is also what [`docs/assistant.md`](../docs/assistant.md) §12's rung 3 wants. The assistant is
a seat at `none`; letting it take part in an exchange is a wider attention and
a `say` in its hands. With reseating that is a host's decision — _this room
lets the assistant speak_ — rather than a code change in the runtime.
Expand All @@ -82,9 +82,9 @@ lets the assistant speak_ — rather than a code change in the runtime.
with the paragraph that says when waking the assistant is worth the money.

**Where.** `wakes` in
[`packages/ambion/src/seat.ts`](packages/ambion/src/seat.ts), `Attention` in
[`types.ts`](packages/ambion/src/types.ts), `seated` in
[`define.ts`](packages/ambion/src/define.ts).
[`packages/ambion/src/seat.ts`](../packages/ambion/src/seat.ts), `Attention` in
[`types.ts`](../packages/ambion/src/types.ts), `seated` in
[`define.ts`](../packages/ambion/src/define.ts).

## Waking the assistant costs money, and nothing says when it is worth it

Expand All @@ -108,27 +108,27 @@ assistant speaks.
summary from a failed activation who asks again gets one message covering
both
exchanges. Nothing pins that behaviour; the tests cover the failure and the
retry separately. See [`docs/assistant.md`](docs/assistant.md) §5.
retry separately. See [`docs/assistant.md`](../docs/assistant.md) §5.

## Exchanges are run state

`Exchanges` holds the open exchange in memory, so a restart begins with none —
right for a room mid-question, and a limit for anything that wants to work
over past exchanges. A closed exchange is an owner and a range, so it is
derivable from the record; nothing derives it today. See
[`docs/exchange.md`](docs/exchange.md) §5.
[`docs/exchange.md`](../docs/exchange.md) §5.

## A second non-seat writer

The room owes summaries through a small scheduler: `owe`, `dueAtQuiescence`,
`dueAfterDraft` and `activationEnded`, held by `Assistant`. If a room-level compactor ever arrives
([`docs/assistant.md`](docs/assistant.md) §16 forbids it by name today), it wants the
([`docs/assistant.md`](../docs/assistant.md) §16 forbids it by name today), it wants the
same scheduler. Two writers is the point at which it should become its own
thing rather than three fields on the session.

## A credentials boundary for tool calls leaving the workspace

**What.** [`docs/workspace.md`](docs/workspace.md) §1 draws the workspace's
**What.** [`docs/workspace.md`](../docs/workspace.md) §1 draws the workspace's
boundary at the sandbox: what a tool can do inside it, through the runtime's
own construction of each `Workspace` value. A tool call that reaches
outside — an external API, a secret, another service — needs a second
Expand All @@ -141,37 +141,37 @@ beyond it.
**Why.** Today a tool's `execute` function reaches whatever a host wires it
to (`docs/agent.md` §3), with no distinction between a call that stays
local and one that leaves. A workspace's filesystem boundary
([`docs/workspace.md`](docs/workspace.md) §1, §8) has no answer for a tool
([`docs/workspace.md`](../docs/workspace.md) §1, §8) has no answer for a tool
that calls out to a real API, and a real deployment needs one before it
hands an agent anything with network access.

**What it needs deciding.**

- Whether this is a workspace concern (a third kind of persistent entity,
under [`docs/workspace.md`](docs/workspace.md) §1's model) or a separate
under [`docs/workspace.md`](../docs/workspace.md) §1's model) or a separate
primitive entirely.
- What a sidecar proxy actually mediates: a network path every outbound
call is forced through, or a narrower set of tools the workspace marks
as external.
- Whether a token is minted per call, per activation, or per agent, and
what a "short-lived" window actually is.
- How this interacts with `ToolContext`
([`docs/workspace.md`](docs/workspace.md) §4) and `defineTool`'s own
([`docs/workspace.md`](../docs/workspace.md) §4) and `defineTool`'s own
`execute` shape (`docs/agent.md` §3), neither of which takes any notion
of a credential today.

**Where.** [`docs/workspace.md`](docs/workspace.md) §1 names the boundary
**Where.** [`docs/workspace.md`](../docs/workspace.md) §1 names the boundary
and scopes it out to this entry; `ToolContext` in
[`docs/workspace.md`](docs/workspace.md) §4 is the most natural place for
[`docs/workspace.md`](../docs/workspace.md) §4 is the most natural place for
a credential to reach a tool call now, alongside `defineTool`'s `execute`
in [`docs/agent.md`](docs/agent.md) §3.
in [`docs/agent.md`](../docs/agent.md) §3.

## `/dev/null` on the just-bash backends is a file

**What.** just-bash treats `/dev/null` as a plain file. A command that
redirects into it appends to it, and on `directoryBackend` the redirect
creates `dev/null` under the root, on disk
([`docs/workspace.md`](docs/workspace.md) §8). The first live run of the
([`docs/workspace.md`](../docs/workspace.md) §8). The first live run of the
example left one there: a product wrote `cat … 2>/dev/null`, and the drive
gained a file.

Expand All @@ -187,17 +187,17 @@ the `bash` tool's own description promises a Unix shell.
- Whether `connect` should seed `/dev` into a `ReadWriteFs` the way just-bash
seeds it into an `InMemoryFs`, so the two backends at least agree.

**Where.** `connectOver` in [`just-bash.ts`](packages/ambion/src/just-bash.ts).
**Where.** `connectOver` in [`just-bash.ts`](../packages/ambion/src/just-bash.ts).

## A backend on a real machine

**What.** A `WorkspaceBackend` whose `connect` creates a real OS user when
one is absent and returns an `env` whose `exec` runs as that user through a
real user switch. [`docs/workspace.md`](docs/workspace.md) §10 sketches it
real user switch. [`docs/workspace.md`](../docs/workspace.md) §10 sketches it
and commits to nothing.

**Why.** The just-bash backend's boundary is nominal
([`docs/workspace.md`](docs/workspace.md) §8): one instance, one identity,
([`docs/workspace.md`](../docs/workspace.md) §8): one instance, one identity,
and nothing stops one agent's `bash` call from reading another's home. A
real user turns that into isolation the operating system enforces, the
guarantee a multi-user Linux box gives. The shape is already fixed: two
Expand All @@ -208,58 +208,15 @@ abort signal because `useradd` and a process spawn are real waits.

- How an OS user is named from an agent's `name`, and what happens when two
workspaces on one host provision the same name
([`docs/workspace.md`](docs/workspace.md) §7 accepts the collision and
([`docs/workspace.md`](../docs/workspace.md) §7 accepts the collision and
checks nothing).
- What `destroy` removes: the users, their homes, or both.
- Whether Pi's `NodeExecutionEnv` is the `env`, with a `sudo -u` prefix on
every command, or whether the backend spawns as the user itself.

**Where.** `WorkspaceBackend` in
[`types.ts`](packages/ambion/src/types.ts); `directoryBackend` in
[`just-bash.ts`](packages/ambion/src/just-bash.ts) is the shape to copy.

## Whether Agent or AgentHarness is Ambion's foundation

**What.** Ambion's runtime imports Pi's lower-level `Agent` class
(`activation.ts`, `seat.ts`), not `AgentHarness`
(`@earendil-works/pi-agent-core`'s `harness/agent-harness.ts`) — a
heavier engine Pi ships beside it, with its own session tree, lanes,
compaction, and tree navigation. Nobody chose `Agent` over `AgentHarness`
on purpose; it is what the runtime already used before this question was
ever asked.

**Why.** [`docs/workspace.md`](docs/workspace.md) §6 reuses part of what
the harness package exports — `Workspace` holds a plain `ExecutionEnv`
as its own `env` property — while keeping `Workspace` itself and
`ToolContext` (§4) outside anything `AgentHarness` provides. That split
holds today because `Agent` and `AgentHarness` overlap only at the edges.
`AgentHarness` also ships its own tool-context mechanism,
`AgentHarnessToolContextSource`, resolved once per turn and handed to an
`AgentHarnessTool`'s `execute`; `ToolContext` covers the same ground at a
narrower scope, resolved once per tool call against `defineTool`'s own
shape. A workspace is the first concept this project has built that sits
this close to ground `AgentHarness` already covers. A concept that sits
closer still is a real possibility once reminders and tasks
(`docs/workspace.md` §11) or anything with its own turn-scoped state
joins it.

**What it needs deciding.**

- Whether `Agent` stays the right foundation once a second and a third
workspace-adjacent concept land, or whether `AgentHarness` already
solves problems this project would otherwise rebuild piece by piece.
- What adopting `AgentHarness` would cost: its own session model
(`SessionTree`, lanes), compaction, and tree navigation, none of which
[`agent.md`](docs/agent.md) or `session.ts` has a use for today.
- Whether `ToolContext`'s own resolution (`docs/workspace.md` §4) should
become Ambion's own provider for `AgentHarnessOptions.toolContext`, if
`Agent` is ever replaced by `AgentHarness`.

**Where.** `packages/ambion/src/activation.ts` and `seat.ts` hold today's
`Agent` imports; [`docs/workspace.md`](docs/workspace.md) §4 and §6 are
where `ExecutionEnv` was adopted without adopting `AgentHarness`; Pi's
own `harness/agent-harness.ts` and `harness/types.ts`
(`@earendil-works/pi-agent-core`) define what `AgentHarness` actually is.
[`types.ts`](../packages/ambion/src/types.ts); `directoryBackend` in
[`just-bash.ts`](../packages/ambion/src/just-bash.ts) is the shape to copy.

## Roles as collaboration patterns

Expand All @@ -282,10 +239,10 @@ collaboration patterns people and agents work in.
differently in two rooms, and a role is probably the same kind of thing.
- What a role changes: the seat's instructions, its attention, or the
order the room wakes seats in. Only the first keeps the routing rule as
one comparison ([`docs/agent.md`](docs/agent.md) rule 6).
one comparison ([`docs/agent.md`](../docs/agent.md) rule 6).
- Who assigns roles. The assistant never runs the room
([`docs/assistant.md`](docs/assistant.md) §2), so assigning roles is a
([`docs/assistant.md`](../docs/assistant.md) §2), so assigning roles is a
different seat's work, or the host's.

**Where.** `seated` in [`define.ts`](packages/ambion/src/define.ts), the
roster in [`render.ts`](packages/ambion/src/render.ts).
**Where.** `seated` in [`define.ts`](../packages/ambion/src/define.ts), the
roster in [`render.ts`](../packages/ambion/src/render.ts).
27 changes: 27 additions & 0 deletions planning/changelog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Changelog

Changes to Ambion, grouped by release. Newest first. **Unreleased** holds
every change since the last release; a release renames that section and
opens a new **Unreleased** above it. [`demos/`](../demos) holds a run
report for a change that warranted one.

Write an entry as a theme, not a merged pull request. State what changed
for somebody building on Ambion, not the mechanism behind it. Group entries
under **Added**, **Changed**, or **Fixed**, and drop a heading with nothing
under it. An internal change — a CI rule, a refactor, a doc rewrite — gets
no entry unless it changes what a person building on Ambion sees.

## Unreleased

### Added

- Five primitives: `defineAgent`, `defineHuman`, `defineTool`,
`defineWorkspace`, `startSession`. Pi owns the model loop, tools and
transcript; just-bash owns the filesystem and shell behind a workspace.
- Presence. A person can join or leave a room, and every seat's context
shows who is in it.
- A workspace per agent: a shared filesystem and shell, in memory or over a
real directory.
- An assistant, seated automatically at `startSession`. It reads a
person's preferences and writes the one message they read when the room
goes quiet.
Loading