@agfpd/iapeer 0.2.98 → 0.2.99
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 +1 -1
- package/docs/01-overview.md +86 -0
- package/docs/02-quickstart.md +110 -0
- package/docs/03-peers-runtimes-identity.md +117 -0
- package/docs/04-lifecycle-and-daemon.md +101 -0
- package/docs/05-system-prompt.md +82 -0
- package/docs/06-storage.md +115 -0
- package/docs/07-install-and-update.md +87 -0
- package/docs/08-peer-management.md +119 -0
- package/docs/09-runtimes.md +103 -0
- package/docs/10-messaging.md +69 -0
- package/docs/11-extensions-and-memory.md +60 -0
- package/docs/12-cli-reference.md +96 -0
- package/docs/13-architecture-and-contract.md +190 -0
- package/docs/14-configuration-and-logs.md +91 -0
- package/docs/README.md +97 -0
- package/docs/ru/01-/320/276/320/261/320/267/320/276/321/200.md +86 -0
- package/docs/ru/02-/320/261/321/213/321/201/321/202/321/200/321/213/320/271-/321/201/321/202/320/260/321/200/321/202.md +110 -0
- package/docs/ru/03-/320/277/320/270/321/200/321/213-/321/200/320/260/320/275/321/202/320/260/320/271/320/274/321/213-/320/270/320/264/320/265/320/275/321/202/320/270/321/207/320/275/320/276/321/201/321/202/321/214.md +117 -0
- package/docs/ru/04-/320/266/320/270/320/267/320/275/320/265/320/275/320/275/321/213/320/271-/321/206/320/270/320/272/320/273-/320/270-/320/264/320/265/320/274/320/276/320/275.md +101 -0
- package/docs/ru/05-/321/201/320/270/321/201/321/202/320/265/320/274/320/275/321/213/320/271-/320/277/321/200/320/276/320/274/320/277/321/202.md +82 -0
- package/docs/ru/06-/321/205/321/200/320/260/320/275/320/265/320/275/320/270/320/265.md +115 -0
- package/docs/ru/07-/321/203/321/201/321/202/320/260/320/275/320/276/320/262/320/272/320/260-/320/270-/320/276/320/261/320/275/320/276/320/262/320/273/320/265/320/275/320/270/320/265.md +87 -0
- package/docs/ru/08-/321/203/320/277/321/200/320/260/320/262/320/273/320/265/320/275/320/270/320/265-/320/277/320/270/321/200/320/260/320/274/320/270.md +119 -0
- package/docs/ru/09-/321/200/320/260/320/275/321/202/320/260/320/271/320/274/321/213.md +103 -0
- package/docs/ru/10-/320/276/320/261/320/274/320/265/320/275-/321/201/320/276/320/276/320/261/321/211/320/265/320/275/320/270/321/217/320/274/320/270.md +69 -0
- package/docs/ru/11-/321/200/320/260/321/201/321/210/320/270/321/200/320/265/320/275/320/270/321/217-/320/270-/320/277/320/260/320/274/321/217/321/202/321/214.md +60 -0
- package/docs/ru/12-cli-reference.md +96 -0
- package/docs/ru/13-/320/260/321/200/321/205/320/270/321/202/320/265/320/272/321/202/321/203/321/200/320/260-/320/270-/320/272/320/276/320/275/321/202/321/200/320/260/320/272/321/202.md +190 -0
- package/docs/ru/14-/320/272/320/276/320/275/321/204/320/270/320/263/321/203/321/200/320/260/321/206/320/270/321/217-/320/270-/320/266/321/203/321/200/320/275/320/260/320/273/321/213.md +91 -0
- package/docs/ru/README.md +97 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
[](./LICENSE)
|
|
7
7
|
[](#quick-start)
|
|
8
8
|
|
|
9
|
-
iapeer keeps a team of AI agents, humans, and services on one host **always reachable — every one of them a single message away.** Each participant is a **peer**: a Claude Code or Codex CLI agent, a person on Telegram, or a scheduler/watcher service. Any agent reaches any other with one call — `send_to_peer(<name>, <text>)` — and a peer that is asleep is still reachable: the daemon wakes it and delivers. Agents on Claude and on Codex — different AI runtimes from different vendors — talk as equal peers; humans take part over Telegram
|
|
9
|
+
iapeer keeps a team of AI agents, humans, and services on one host **always reachable — every one of them a single message away.** Each participant is a **peer**: a Claude Code or Codex CLI agent, a person on Telegram, or a scheduler/watcher service. Any agent reaches any other with one call — `send_to_peer(<name>, <text>)` — and a peer that is asleep is still reachable: the daemon wakes it and delivers. Agents on Claude and on Codex — different AI runtimes from different vendors — talk as equal peers; humans take part over Telegram, or directly in the terminal — attached to a peer's live session with `iapeer attach`.
|
|
10
10
|
|
|
11
11
|
## How it works
|
|
12
12
|
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# 01 — Overview
|
|
2
|
+
|
|
3
|
+
[Русский](ru/01-обзор.md) · **English**
|
|
4
|
+
|
|
5
|
+
iapeer keeps a team of agents on one host: it maintains a registry of participants, routes messages between them, and manages their presence. Three concepts define the whole system — peer, runtime, identity.
|
|
6
|
+
|
|
7
|
+
## Peer
|
|
8
|
+
|
|
9
|
+
A peer is a participant in the system. It's an AI agent (Claude, Codex), a human (over Telegram), or a service process (a scheduler, a watcher). At the transport level they're indistinguishable: addressed the same way, exchanging messages over one protocol.
|
|
10
|
+
|
|
11
|
+
A peer has a name (`personality`) — a short lowercase logical name (`assistant`, `code-reviewer`). The name is stable across runtimes, derived from the working-folder name, and unique: two peers can't share one name.
|
|
12
|
+
|
|
13
|
+
A peer's nature is set by the `intelligence` attribute — it travels in the envelope of every message, so the recipient knows who it's talking to with no out-of-band agreement:
|
|
14
|
+
|
|
15
|
+
| Value | Who |
|
|
16
|
+
|---|---|
|
|
17
|
+
| `artificial` | an AI agent (Claude, Codex) |
|
|
18
|
+
| `natural` | a human (over Telegram) |
|
|
19
|
+
| `absent` | a program with no intelligence (a scheduler, a watcher) |
|
|
20
|
+
|
|
21
|
+
## Runtime
|
|
22
|
+
|
|
23
|
+
A runtime is a peer's mode of presence. One peer can be present in several runtimes. By lifecycle management, runtimes split into two classes:
|
|
24
|
+
|
|
25
|
+
- **claude, codex** — AI agents. Their session isn't kept running all the time: the daemon brings the agent up on an incoming message and closes the session after an hour idle. The daemon governs this class.
|
|
26
|
+
- **telegram, notifier** — service router runtimes (not AI). They run continuously: the Telegram bridge accepts inbound messages at any time, notifier fires on a schedule. launchd holds them with auto-restart. These are the infrastructure runtimes; the daemon doesn't touch them.
|
|
27
|
+
|
|
28
|
+
The boundary is a split of responsibility: the daemon manages the claude/codex agents, launchd the infrastructure runtimes. That's why `stop`/`start`/`new` behave differently depending on the class.
|
|
29
|
+
|
|
30
|
+
## Identity
|
|
31
|
+
|
|
32
|
+
Identity is the address of a peer's live presence, a join of runtime and name:
|
|
33
|
+
|
|
34
|
+
```text
|
|
35
|
+
PEER_IDENTITY = PEER_RUNTIME + "-" + PEER_PERSONALITY
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The agent `assistant` on Claude is `claude-assistant`, the same one on Codex is `codex-assistant`: one name, different identities. Parsing goes by the first dash (a runtime name has no dashes, a peer name may), so `claude-code-reviewer` is the runtime `claude` and the peer `code-reviewer`.
|
|
39
|
+
|
|
40
|
+
Three values — `PEER_PERSONALITY`, `PEER_RUNTIME`, `PEER_IDENTITY` — are passed into the session's environment. By them any component knows its own name and on whose behalf it acts. Details — [03 — Peers, runtimes, identity](03-peers-runtimes-identity.md).
|
|
41
|
+
|
|
42
|
+
## Components
|
|
43
|
+
|
|
44
|
+
```text
|
|
45
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
46
|
+
│ CLI iapeer <command> │
|
|
47
|
+
│ create · list · send · stop/start · new · connect · enable … │
|
|
48
|
+
└───────────────┬──────────────────────────────────────────────┘
|
|
49
|
+
│
|
|
50
|
+
┌───────────────▼───────────────┐ ┌──────────────────────────┐
|
|
51
|
+
│ Router daemon (runs 24/7) │ │ Peer registry │
|
|
52
|
+
│ • accepts send_to_peer │◀───▶│ ~/.iapeer/ │
|
|
53
|
+
│ • finds the recipient │ │ peers-profiles.json │
|
|
54
|
+
│ • delivers / wakes │ └──────────────────────────┘
|
|
55
|
+
│ • supervises peers │
|
|
56
|
+
└───────────────┬───────────────┘
|
|
57
|
+
│ wakes / sleeps
|
|
58
|
+
┌───────────────▼───────────────────────────────────────────────┐
|
|
59
|
+
│ Runtime adapters │
|
|
60
|
+
│ claude · codex (pty supervisor) │ telegram · notifier (launchd)│
|
|
61
|
+
└────────────────────────────────────────────────────────────────┘
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**The router daemon** — the only continuously running core process, under launchd. It accepts `send_to_peer` calls, finds the recipient in the registry, and delivers the message into a live session or wakes a sleeping agent. Once a minute it walks the peers: it closes sessions after an hour idle, restarts those marked for a fresh start, and keeps a crashed agent from entering a restart loop. See [04 — Lifecycle and the daemon](04-lifecycle-and-daemon.md).
|
|
65
|
+
|
|
66
|
+
**The peer registry** — `~/.iapeer/peers-profiles.json`: each peer's name, runtimes, description, nature, working folder. The source of truth is each peer's own profile (`<folder>/.iapeer/peer-profile.json`); the registry is reprojected from the profiles. See [06 — Storage](06-storage.md).
|
|
67
|
+
|
|
68
|
+
**Runtime adapters** — the session-launch layer. For Claude and Codex it's a TUI session under a pty supervisor with a swapped-in system prompt; for Telegram and notifier, a long-lived router process under launchd. The adapters hide the runtimes' differences behind one interface. See [09 — Runtimes](09-runtimes.md).
|
|
69
|
+
|
|
70
|
+
**The `iapeer` CLI** — the entry point for the operator and for agents: creating peers, reviewing peers and their state, manual sending, stopping and restarting, connecting Telegram, installing extensions. See [12 — CLI](12-cli-reference.md).
|
|
71
|
+
|
|
72
|
+
## A message's path
|
|
73
|
+
|
|
74
|
+
1. The sender calls `send_to_peer(assistant, "...")` — the call goes to the daemon over a local socket or HTTP.
|
|
75
|
+
2. The daemon finds `assistant` in the registry and checks for a live session.
|
|
76
|
+
3. **Alive** — the message is inserted into the session, the daemon confirms receipt and answers `delivered`.
|
|
77
|
+
4. **Asleep** — the daemon wakes the session (resuming the prior conversation or starting fresh), delivers, and answers `delivered, woke`.
|
|
78
|
+
5. **Stopped** by the operator — the daemon refuses delivery and tells the sender directly.
|
|
79
|
+
|
|
80
|
+
A `delivered` answer means the message landed in a live session — there's normally no "for later" queue. The mechanics are covered in [10 — Messaging](10-messaging.md).
|
|
81
|
+
|
|
82
|
+
## Next
|
|
83
|
+
|
|
84
|
+
- Try it — [02 — Quick start](02-quickstart.md).
|
|
85
|
+
- Understand the concepts deeper — [03 — Peers, runtimes, identity](03-peers-runtimes-identity.md).
|
|
86
|
+
- Build an extension or a runtime package — [13 — Architecture and contract](13-architecture-and-contract.md).
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# 02 — Quick start
|
|
2
|
+
|
|
3
|
+
[Русский](ru/02-быстрый-старт.md) · **English**
|
|
4
|
+
|
|
5
|
+
Four steps — from an empty host to an agent you can message. Each step is one command; under it, what it does.
|
|
6
|
+
|
|
7
|
+
## Requirements
|
|
8
|
+
|
|
9
|
+
- **macOS.** Process presence is managed by launchd, so for now only macOS is supported.
|
|
10
|
+
- **[bun](https://bun.sh)** on `PATH`. Needed for install and updates: the package is installed from source (it ships no precompiled JS), and the `iapeer` binary is built via `bun build --compile`. The installed `~/.local/bin/iapeer` is itself a standalone binary and no longer needs bun at runtime.
|
|
11
|
+
- **`~/.local/bin` on `PATH`.** The `iapeer` binary is installed here. If the directory isn't on `PATH`, the `iapeer` command won't be found after install — add it: `export PATH="$HOME/.local/bin:$PATH"` in your shell profile.
|
|
12
|
+
- **At least one agent runtime** — Claude Code or Codex CLI. Each AI peer is a live session of one of them; if neither is installed, `iapeer create` and `iapeer init` say so with an error rather than failing silently later.
|
|
13
|
+
|
|
14
|
+
## Step 1. Install the core
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npx @agfpd/iapeer
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The call with no arguments is the install. It does three things and starts nothing:
|
|
21
|
+
|
|
22
|
+
1. Builds the `iapeer` binary and places it in `~/.local/bin/iapeer` (the previous version stays beside it as `iapeer.prev`, for rollback).
|
|
23
|
+
2. Creates the `~/.iapeer/` layout — directories for the registry, state, logs, cache, and runtimes.
|
|
24
|
+
3. Writes the daemon plist to `~/Library/LaunchAgents/com.agfpd.iapeer.plist` but **doesn't load** it — that's what `iapeer onboard` does in step 2.
|
|
25
|
+
|
|
26
|
+
The install is idempotent: a re-run updates the binary and rebuilds the layout without breaking anything.
|
|
27
|
+
|
|
28
|
+
## Step 2. Set up the host
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
iapeer onboard
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`onboard` prepares the host for agents and is required: first of all it brings up the router daemon, without which messages aren't delivered. What it does, in order:
|
|
35
|
+
|
|
36
|
+
- **Daemon.** Loads the plist into launchd (idempotent) and verifies the router answers. From then on launchd brings the daemon up itself — after a crash and after a machine reboot. This starts a persistent background service, so macOS shows an "iapeer added a background item" notice — that's expected.
|
|
37
|
+
- **Marketplace.** Registers the agfpd extension marketplace in Claude and Codex so agents can later install capability plugins.
|
|
38
|
+
- **notifier.** Provisions the scheduler infrastructure runtime: the service peers `timer` (fires on a schedule) and `watcher` (watches for events) appear.
|
|
39
|
+
- **Telegram.** Sets up a human peer — that's you, the owner — present over Telegram. This step is interactive: you'll need your Telegram user-id.
|
|
40
|
+
- **Memory.** Installs the default memory provider if the slot is free.
|
|
41
|
+
|
|
42
|
+
The daemon start always runs; the rest are on by default but each can be turned off with a flag — `--no-notifier`, `--no-telegram`, `--no-memory`. To see what the command would do without changing anything:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
iapeer onboard --dry-run
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Check the daemon is alive and the host is ready:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
iapeer status
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
You'll see a host snapshot: the core version, the daemon's health, the memory-slot state.
|
|
55
|
+
|
|
56
|
+
## Step 3. Create your first agent
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
iapeer create assistant --runtime claude
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
This is how the peer `assistant` appears, on the Claude runtime. What the command sets up:
|
|
63
|
+
|
|
64
|
+
- a working folder — by default `~/.iapeer/peers/assistant`;
|
|
65
|
+
- the peer profile `peer-profile.json` — name, runtimes, description, nature;
|
|
66
|
+
- a registry entry, so the daemon knows about the peer;
|
|
67
|
+
- a connection to the daemon over MCP — through which the agent gets the `send_to_peer` tool;
|
|
68
|
+
- a doctrine file `IAPEER.md` — the agent's role and character (a peer's `CLAUDE.md`-equivalent), which you fill in later;
|
|
69
|
+
- shared memory, if the memory slot is claimed.
|
|
70
|
+
|
|
71
|
+
The agent is created, but the session isn't running yet — it comes up on the first message.
|
|
72
|
+
|
|
73
|
+
`IAPEER.md` is to a peer what a `CLAUDE.md` is to a project — its instructions and character. The one in `~/.iapeer/` applies to every peer on the host; the one in a peer's folder applies to that peer alone. And any other `.md` you drop in the root of a peer's `.iapeer/` folder (or the host's `~/.iapeer/`) rides into the system prompt too — so you can hand an agent extra context just by adding a file. Details in [05 — Identity and the system prompt](05-system-prompt.md).
|
|
74
|
+
|
|
75
|
+
You can also create an agent from its working folder, with no path, using `iapeer init`. The difference between `create` and `init` is covered in [08 — Peer management](08-peer-management.md).
|
|
76
|
+
|
|
77
|
+
**Want the agent to reply in Telegram?** Connect a bot to it and you message the agent from the messenger — see the [telegram-runtime quickstart](https://github.com/agfpd/telegram-runtime/blob/main/docs/02-quickstart.md).
|
|
78
|
+
|
|
79
|
+
## Step 4. Send the first message
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
iapeer send assistant --message "hi, are you there?"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
There's no live session yet, so the daemon brings one up and delivers the message. In reply you'll see `delivered to assistant (claude)` — or `queued …` if the agent turned out to be busy.
|
|
86
|
+
|
|
87
|
+
See which peers exist and who's alive right now:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
iapeer list
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Each line is a peer, its runtimes, and its state: `●` alive, `○` asleep, `✕` stopped.
|
|
94
|
+
|
|
95
|
+
Join an agent's session live, like an ordinary terminal:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
iapeer attach assistant
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The session opens under the supervisor; detach with `Ctrl-]` — or just close the terminal. Either way the session keeps running in the background, and you reattach with `iapeer attach` whenever you need it.
|
|
102
|
+
|
|
103
|
+
## What's next
|
|
104
|
+
|
|
105
|
+
The basic loop is closed: the host is ready, the agent is created, the message is delivered. From here, by task:
|
|
106
|
+
|
|
107
|
+
- What happens on wake and sleep — [04 — Lifecycle and the daemon](04-lifecycle-and-daemon.md).
|
|
108
|
+
- Fill in the agent's role and understand the system prompt — [05 — Identity and the system prompt](05-system-prompt.md).
|
|
109
|
+
- Connect an agent to Telegram, add a second runtime, switch all peers to another runtime — [09 — Runtimes](09-runtimes.md).
|
|
110
|
+
- Install an extension or memory for an agent — [11 — Extensions and memory](11-extensions-and-memory.md).
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# 03 — Peers, runtimes, identity
|
|
2
|
+
|
|
3
|
+
[Русский](ru/03-пиры-рантаймы-идентичность.md) · **English**
|
|
4
|
+
|
|
5
|
+
A deeper take on the three base concepts from the [overview](01-overview.md) — at the level of detail you need to create peers, switch them between runtimes, and read profiles.
|
|
6
|
+
|
|
7
|
+
## Peer
|
|
8
|
+
|
|
9
|
+
A peer is a participant in the system. It has a working folder, a name, a set of runtimes, and a nature.
|
|
10
|
+
|
|
11
|
+
### Name (personality)
|
|
12
|
+
|
|
13
|
+
The name is a short lowercase logical name: `assistant`, `code-reviewer`, `timer`. The format follows `^[a-z][a-z0-9-]{0,31}$`: a letter, then letters, digits, dashes, up to 32 characters.
|
|
14
|
+
|
|
15
|
+
The name isn't stored as a separate value — it's **derived from the working-folder name**. The folder `~/.iapeer/peers/assistant` yields the name `assistant`; non-Latin and other characters are normalized to an allowed form.
|
|
16
|
+
|
|
17
|
+
Hence a strict rule: **the name and the working folder are one-to-one**. A name already taken by another peer is refused — no silent suffix to resolve a collision. The name is an address, and it must be unambiguous.
|
|
18
|
+
|
|
19
|
+
### Nature (intelligence)
|
|
20
|
+
|
|
21
|
+
Every peer carries a nature attribute. It travels with the message, so the recipient knows who it's talking to without guessing.
|
|
22
|
+
|
|
23
|
+
| Value | Who it is |
|
|
24
|
+
|---|---|
|
|
25
|
+
| `artificial` | an AI agent (Claude, Codex) |
|
|
26
|
+
| `natural` | a human (over Telegram and similar channels) |
|
|
27
|
+
| `absent` | a program with no intelligence (a scheduler, a watcher, a webhook) |
|
|
28
|
+
|
|
29
|
+
The nature defaults from the runtime: agent runtimes give `artificial`, Telegram `natural`, notifier `absent`. You can set it explicitly when creating a peer.
|
|
30
|
+
|
|
31
|
+
The values `human` and `scripted` were used before — they're still read (understood as `natural` and `absent` respectively), but on write the system keeps whatever value is already in the profile and doesn't rewrite peers already set up.
|
|
32
|
+
|
|
33
|
+
## Runtime
|
|
34
|
+
|
|
35
|
+
A runtime is a peer's mode of presence. A runtime name is shorter than a peer name: the format `^[a-z][a-z0-9]{0,31}$`, no dashes. That matters for parsing identity (below).
|
|
36
|
+
|
|
37
|
+
### Two runtime classes
|
|
38
|
+
|
|
39
|
+
**claude, codex** — AI agents. Their session isn't kept running all the time: the daemon brings the agent up on an incoming message and closes the session after an hour idle. This runtime the daemon manages fully — it wakes and sleeps it.
|
|
40
|
+
|
|
41
|
+
**telegram, notifier** — router runtimes, not AI. The Telegram bridge accepts inbound messages at any time, notifier fires on a schedule — so they run continuously under launchd with auto-restart. Such a runtime the daemon **doesn't touch**: wake and sleep don't apply to it, its lifecycle is governed by launchd. These are the infrastructure runtimes.
|
|
42
|
+
|
|
43
|
+
The split is a responsibility boundary: the daemon owns claude/codex, launchd the infrastructure runtimes. The `stop`/`start`/`new` commands behave differently depending on the class (see [08 — Peer management](08-peer-management.md)).
|
|
44
|
+
|
|
45
|
+
### Several runtimes on one peer
|
|
46
|
+
|
|
47
|
+
A peer can declare several runtimes. In the profile that's two fields:
|
|
48
|
+
|
|
49
|
+
- **`runtimes`** — the list of all runtimes the peer can be present on, e.g. `["claude", "codex"]`.
|
|
50
|
+
- **`default_runtime`** — the primary runtime: where messages route by default and which runtime comes up first.
|
|
51
|
+
|
|
52
|
+
Adding a runtime to a peer and making it the primary are different operations. The first (`add-runtime`) extends the options without changing routing; the second (`default-runtime`) switches the primary runtime. Switching `default_runtime` across all peers in bulk is exactly the moment of "transplanting" the team from one agent runtime to another. See [09 — Runtimes](09-runtimes.md).
|
|
53
|
+
|
|
54
|
+
The runtime list also fills itself: when a peer is initialized, the system scans its folder for runtime markers (the `.claude`, `.codex` directories and subfolders under `runtimes/`) and adds what it finds to `runtimes`. So the list stays current even when a runtime appeared on a peer organically rather than being declared explicitly.
|
|
55
|
+
|
|
56
|
+
## Identity
|
|
57
|
+
|
|
58
|
+
Identity is the address of a specific live presence of a peer. It's composed of the runtime and the name through a dash:
|
|
59
|
+
|
|
60
|
+
```text
|
|
61
|
+
PEER_IDENTITY = PEER_RUNTIME + "-" + PEER_PERSONALITY
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The peer `assistant` on Claude is `claude-assistant`; the same one on Codex is `codex-assistant`. One name, two identities.
|
|
65
|
+
|
|
66
|
+
Identity is parsed **by the first dash**: everything to the left is the runtime, everything to the right is the name. Since a runtime name has no dashes while a peer name may, the parse is unambiguous: `claude-code-reviewer` is the runtime `claude` and the peer `code-reviewer`.
|
|
67
|
+
|
|
68
|
+
### The identity ABI
|
|
69
|
+
|
|
70
|
+
Three values form an ABI — a set of environment variables by which any component of the system knows who it is:
|
|
71
|
+
|
|
72
|
+
| Variable | Value | Example |
|
|
73
|
+
|---|---|---|
|
|
74
|
+
| `PEER_PERSONALITY` | the peer name | `assistant` |
|
|
75
|
+
| `PEER_RUNTIME` | the current session's runtime | `claude` |
|
|
76
|
+
| `PEER_IDENTITY` | the full address | `claude-assistant` |
|
|
77
|
+
|
|
78
|
+
When the daemon brings up an agent session, it passes these variables into the environment. By them the agent knows its name and on whose behalf it sends messages. The current session's runtime is resolved in this order: from the identity header (`PEER_IDENTITY`), then from the profile, then from `PEER_RUNTIME`, and last by autodetection from runtime markers in the environment — Codex sets `CODEX_THREAD_ID`, Claude sets `CLAUDECODE=1`. If no source works, the wake is refused with a demand to set `PEER_RUNTIME` or create a profile.
|
|
79
|
+
|
|
80
|
+
## Profile and registry
|
|
81
|
+
|
|
82
|
+
Identity has two storage places, and it matters which is authoritative.
|
|
83
|
+
|
|
84
|
+
### The peer profile — the source of truth
|
|
85
|
+
|
|
86
|
+
`<peer-folder>/.iapeer/peer-profile.json` is the peer's card, the single source of truth about it. Here's a real profile:
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"personality": "assistant",
|
|
91
|
+
"runtimes": ["codex", "claude"],
|
|
92
|
+
"default_runtime": "claude",
|
|
93
|
+
"runtime": "claude",
|
|
94
|
+
"description": "General-purpose assistant. Takes tasks over IAP.",
|
|
95
|
+
"intelligence": "artificial",
|
|
96
|
+
"initial_prompt": "New session. Greet the owner.",
|
|
97
|
+
"interfaces": {
|
|
98
|
+
"telegram": { "bot": "assistant", "bot_username": "assistant_bot" }
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The profile fields split into three classes by who owns them:
|
|
104
|
+
|
|
105
|
+
- **Identity core** — `personality`, `default_runtime`, `runtimes`, `description`, `intelligence`, `initial_prompt`. Owned by the core and the lifecycle. The `initial_prompt` field is the seed for a fresh session's first turn. The `runtime` field beside `default_runtime` is a synchronized copy for compatibility with old readers; both are written consistently.
|
|
106
|
+
- **Private plugin config** — sections like `expansion` (shortcut aliases), `notifier` (the scheduler peer's settings), and others, one per plugin. Read only from the local profile, never projected into the registry.
|
|
107
|
+
- **Public passport** — the `interfaces` section (e.g. `interfaces.telegram.bot`). These are routing attributes, and they're projected into the registry.
|
|
108
|
+
|
|
109
|
+
The `description` is length-bounded (currently 450 characters) — it's self-documenting, seen by other peers in the registry.
|
|
110
|
+
|
|
111
|
+
### The registry — a derivative
|
|
112
|
+
|
|
113
|
+
`~/.iapeer/peers-profiles.json` is the shared registry of all peers. The daemon reads it to know whom to deliver a message to and what runtimes a peer has. A registry entry holds a projection of the profile: name, primary runtime, runtime list, description, nature, working folder, public interfaces.
|
|
114
|
+
|
|
115
|
+
The registry isn't the source of truth but a **projection** of the local profiles. It can be reprojected from the profiles at any time (`iapeer verify --fix`). If the registry and a profile diverge, the profile wins — the registry is fixed from it.
|
|
116
|
+
|
|
117
|
+
The registry is written only through a single writer under a file lock — editing `peers-profiles.json` directly is forbidden at the core level. This rules out races when several processes change the peer set at once. More on layout and ownership — [06 — Storage](06-storage.md).
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# 04 — Lifecycle and the daemon
|
|
2
|
+
|
|
3
|
+
[Русский](ru/04-жизненный-цикл-и-демон.md) · **English**
|
|
4
|
+
|
|
5
|
+
## The router daemon
|
|
6
|
+
|
|
7
|
+
The daemon is the only continuously running core process. It has three roles:
|
|
8
|
+
|
|
9
|
+
1. **Routing** — accepts `send_to_peer`, finds the recipient in the registry, delivers.
|
|
10
|
+
2. **Waking** — brings up a sleeping recipient's session and delivers after the wake.
|
|
11
|
+
3. **Supervision** — once a minute walks the peers: sleeps the idle, notices the crashed, restarts those marked for a fresh start.
|
|
12
|
+
|
|
13
|
+
launchd holds the daemon by the plist `com.agfpd.iapeer` — separate from the peers' plists. launchd brings the daemon up at system boot and restarts it on a crash, so the router survives both a machine reboot and its own failures.
|
|
14
|
+
|
|
15
|
+
The daemon listens on two inputs at once:
|
|
16
|
+
|
|
17
|
+
- the **local socket** `~/.iapeer/state/iapeer/router.sock` (owner-only access) — for the CLI and the router runtimes;
|
|
18
|
+
- **TCP on loopback** `127.0.0.1:8765` — for Claude and Codex agents connected to `send_to_peer` over HTTP.
|
|
19
|
+
|
|
20
|
+
### The supervisory tick
|
|
21
|
+
|
|
22
|
+
Once a minute the daemon runs a supervisory pass. In one tick it sleeps agents idle longer than an hour; notices dead sessions and decides their fate; brings up fresh those marked for an immediate restart; and feeds the ephemeral-worker queues. A minute is the ceiling on latency for deferred work: a queue that piled up during the daemon's restart window is drained on the next tick.
|
|
23
|
+
|
|
24
|
+
## Peer states
|
|
25
|
+
|
|
26
|
+
A claude/codex agent is, at any moment, in one of three states:
|
|
27
|
+
|
|
28
|
+
| State | What it means | Marker in `iapeer list` |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| **alive** | the session is running and answering | `●` |
|
|
31
|
+
| **asleep** | no session, the peer is ready to wake on a message | `○` |
|
|
32
|
+
| **stopped** | the operator set a stop flag; the daemon won't wake it | `✕` |
|
|
33
|
+
|
|
34
|
+
```text
|
|
35
|
+
message
|
|
36
|
+
asleep ──────────────▶ alive
|
|
37
|
+
▲ │
|
|
38
|
+
│ stop flag cleared │ an hour idle / crash
|
|
39
|
+
│ ▼
|
|
40
|
+
stopped ◀─────────────── alive
|
|
41
|
+
stop (operator)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Delivery and waking
|
|
45
|
+
|
|
46
|
+
On an incoming message the daemon looks at the recipient's state:
|
|
47
|
+
|
|
48
|
+
- **alive** — inserts into the session, confirms receipt, answers `delivered`;
|
|
49
|
+
- **asleep** — brings up the session, re-checks the recipient, delivers, answers `delivered, woke`;
|
|
50
|
+
- **stopped** — refuses and tells the sender directly.
|
|
51
|
+
|
|
52
|
+
The guarantee "`delivered` means it landed in a live session" is covered in [10 — Messaging](10-messaging.md).
|
|
53
|
+
|
|
54
|
+
### A fresh start or resuming the conversation
|
|
55
|
+
|
|
56
|
+
When bringing up a sleeper, the daemon chooses: start a **fresh** session or **resume** the prior conversation. It depends on what's waking it, the peer type, and — for agents — the **topic** of the incoming message.
|
|
57
|
+
|
|
58
|
+
Three peer types, three base behaviors: an **ephemeral worker** always starts fresh, a **human** (Telegram) always resumes, an **agent** resumes only on a matching topic. The full expansion:
|
|
59
|
+
|
|
60
|
+
| What woke it | Condition | Result |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| `iapeer <runtime>` (from the folder) | — | 🆕 fresh |
|
|
63
|
+
| `iapeer attach` | — | ▶️ resume (error if there's nothing to resume) |
|
|
64
|
+
| a message | crashed / self-closed / never started | 🆕 fresh |
|
|
65
|
+
| a message | idle-slept, **human** peer (Telegram) | ▶️ resume (always) |
|
|
66
|
+
| a message | idle-slept, **agent** · topic matched a session thread | ▶️ resume |
|
|
67
|
+
| a message | idle-slept, **agent** · topic is new or absent | 🆕 fresh |
|
|
68
|
+
| a message | peer stopped (`stop`) | 🚫 not woken — explicit refusal |
|
|
69
|
+
|
|
70
|
+
The key for agents: **resume is opt-in — through the topic**. A message with no topic after idle brings up a fresh session rather than reviving old context (otherwise a session days old would wake and carry on the wrong work). To resume the right thread, the sender names its topic. Topics **accumulate**: one session can run several threads, and a ping on any of them (not just the latest) resumes it. For human peers resume is always — a topic doesn't reset them.
|
|
71
|
+
|
|
72
|
+
The lazy reset-to-fresh is **`iapeer refresh`** (see "Lifecycle commands" below): it marks a peer, and on its next natural wake it starts fresh instead of resuming — for any type, including humans-on-Telegram. So after a doctrine update the team re-reads it on its own, with no manual `/new` and no mass wake.
|
|
73
|
+
|
|
74
|
+
## Idle sleep
|
|
75
|
+
|
|
76
|
+
An agent with no activity for longer than an hour is slept on the next supervisory tick: the daemon closes the session and sets a sleep marker. Memory is freed, the conversation is saved to disk — the next message **with that work's topic** brings the agent up exactly where it stopped (without a topic, or with a new one, it's fresh — see above). The hour idle is the only time boundary: there's no "blind" lifetime TTL. The idle threshold defaults to 3600 seconds; it and the other lifecycle thresholds (boot, readiness, restart-loop protection) are tunable via environment variables — [14 — Configuration and logs](14-configuration-and-logs.md).
|
|
77
|
+
|
|
78
|
+
## Supervision and resilience
|
|
79
|
+
|
|
80
|
+
**Restart-loop protection.** The daemon keeps a ring of each peer's recent deaths. If a peer dies 3 times in 5 minutes, the daemon stops bringing it up and leaves it asleep — that's a fault signal, not a hundredth blind retry.
|
|
81
|
+
|
|
82
|
+
**Wake serialization.** Several messages may hit a sleeping agent at once; one attempt should bring it up, or two competing sessions would be born. The daemon takes a lock on waking a given peer — the rest wait for the first to be ready.
|
|
83
|
+
|
|
84
|
+
**Postmortem diagnostics.** At the moment a session dies the supervisor records its cause (exit code / signal) — what an ordinary "is the process alive" check misses. For this a separate canary holds a blocking connection to the supervisor and, on a session's death, writes the circumstances — memory, swap, process list, diagnostic reports — to a log.
|
|
85
|
+
|
|
86
|
+
## The boundary: claude/codex vs infrastructure
|
|
87
|
+
|
|
88
|
+
The daemon manages only claude/codex agents, and only those whose plist belongs to the core. Infrastructure peers (Telegram, notifier) and any peer under a foreign launchd plist are off-limits to the daemon: it doesn't wake, sleep, or restart them. Their lifecycle is governed by launchd.
|
|
89
|
+
|
|
90
|
+
So `stop`, `start`, `new` refuse to touch foreign launchd peers — for those the core is only a reader. This guards against the core fighting launchd over process control.
|
|
91
|
+
|
|
92
|
+
## Lifecycle commands
|
|
93
|
+
|
|
94
|
+
- **`iapeer stop` / `start`** — stop (stop flag + close the session) and clear the stop. For infrastructure peers — through launchd.
|
|
95
|
+
- **`iapeer new`** — an unconditional fresh restart, the emergency lever for a hung or dead session; acts mechanically, around the agent.
|
|
96
|
+
- **`iapeer refresh <peer> \| --all`** — a lazy "soft restart": mark a peer for a fresh start on its NEXT natural wake (re-read the doctrine/fragments from disk), without killing a live session or waking the rest. Unlike `new` (an immediate hard restart), this is a deferred, unobtrusive lever: used after a doctrine release so the team refreshes on its own. The marker overrides "resume" for all peer types, including humans-on-Telegram (who otherwise resume the old session until an explicit `/new`).
|
|
97
|
+
- **`iapeer compact`** — compact the conversation; a sleeper is resumed first, then compacted.
|
|
98
|
+
- **`iapeer interrupt`** — interrupt the current turn (Escape) without losing context.
|
|
99
|
+
- **`iapeer self-fresh` / `self-done`** — an agent's self-calls: mark itself for a fresh restart, or quietly finish (for ephemeral workers).
|
|
100
|
+
|
|
101
|
+
Behavior by runtime class — [08 — Peer management](08-peer-management.md); ephemeral workers — [10 — Messaging](10-messaging.md).
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# 05 — Identity and the system prompt
|
|
2
|
+
|
|
3
|
+
[Русский](ru/05-системный-промпт.md) · **English**
|
|
4
|
+
|
|
5
|
+
So an agent knows its name, role, and teammates, iapeer assembles a system prompt for it — the text the runtime receives before the conversation starts. The prompt is built from layers: some written by a human, some assembled by the system.
|
|
6
|
+
|
|
7
|
+
## Why swap the system prompt
|
|
8
|
+
|
|
9
|
+
Claude and Codex have their own built-in system prompt — the one that makes them a "coding assistant". iapeer replaces it entirely: instead of the native prompt, the runtime gets a text assembled by iapeer where the agent is described as a team peer — with its name, role, knowledge of its neighbors, and shared rules.
|
|
10
|
+
|
|
11
|
+
The swap goes through each runtime's standard mechanism:
|
|
12
|
+
|
|
13
|
+
- **Claude** — the `--system-prompt-file <file>` flag;
|
|
14
|
+
- **Codex** — the `model_instructions_file` setting (`codex -c model_instructions_file=<file>`), which replaces the compiled base instructions.
|
|
15
|
+
|
|
16
|
+
Infrastructure runtimes (Telegram, notifier) get no system prompt — they aren't language models, there's nothing to fill for them.
|
|
17
|
+
|
|
18
|
+
## The prompt layers
|
|
19
|
+
|
|
20
|
+
The prompt is assembled from layers, general to specific. Each layer, when empty, simply drops out — no stray sections appear.
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
1. Identity block (YAML) ← system, always fresh
|
|
24
|
+
2. IAPEER.md doctrine ← human: shared + personal
|
|
25
|
+
3. Peer registry ← system: who else is on the team
|
|
26
|
+
4. Domain files (any .md by name) ← human / plugin
|
|
27
|
+
5. Doctrine fragments ← primitive plugins, machine-generated
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Layer 1 — the identity block
|
|
31
|
+
|
|
32
|
+
A dynamic YAML block at the top of the prompt. The system synthesizes it on each launch from the peer profile and host facts: name, description, working folder, platform, OS version, user, machine name, today's date. Values are escaped, so colons, quotes, and newlines in the description don't break the markup.
|
|
33
|
+
|
|
34
|
+
This layer is always fresh — the date and host facts are substituted anew on each launch.
|
|
35
|
+
|
|
36
|
+
### Layer 2 — the IAPEER.md doctrine
|
|
37
|
+
|
|
38
|
+
The main layer a human fills — a peer's equivalent of a project's `CLAUDE.md`: the agent's standing instructions and character. It's two files:
|
|
39
|
+
|
|
40
|
+
- **The shared doctrine** `~/.iapeer/IAPEER.md` — rules in force for all the host's peers: how to answer an approach, how to handle memory, the team's shared agreements.
|
|
41
|
+
- **The personal doctrine** `<peer-folder>/.iapeer/IAPEER.md` — the role and character of the specific peer: who it is, what it does, its areas of responsibility.
|
|
42
|
+
|
|
43
|
+
The order is general to specific: the shared doctrine first, then the personal. The specific comes after the general and refines it. Each file is inserted as its own marked section with a path label (an HTML comment with the file path, visible to the model), so the agent understands where each rule came from.
|
|
44
|
+
|
|
45
|
+
Both files are edited by the operator in ordinary markdown. This is where you define who the agent will be.
|
|
46
|
+
|
|
47
|
+
### Layer 3 — the peer registry
|
|
48
|
+
|
|
49
|
+
The system inserts a short summary of every team peer: name, primary runtime, runtime list, description, nature. So the agent knows whom it can reach via `send_to_peer` and who does what. The summary is sorted by name; if there are no peers, the layer drops out.
|
|
50
|
+
|
|
51
|
+
This is where the self-documenting `description` from the profile does its work: it's length-bounded precisely because it lands in every peer's prompt — bloated descriptions would cost the whole team context.
|
|
52
|
+
|
|
53
|
+
For a service peer — the notifier's `timer` and `watcher`, say — this description carries the call format itself: the JSON fields and an example. So an agent knows how to drive the service from its prompt alone, and when it needs more it can just ask the service (which replies to `help` with its full format). The registry teaches the call; the peer documents the rest.
|
|
54
|
+
|
|
55
|
+
### Layer 4 — domain files by name
|
|
56
|
+
|
|
57
|
+
Any markdown file in the root of `.iapeer/` — except `IAPEER.md` — rides into the prompt as its own section. This channel is open to you, not just plugins: drop a `SOUL.md` (or any name you like) in `~/.iapeer/` and every peer on the host sees it; drop it in a single peer's `.iapeer/` folder and only that peer does. Handing an agent extra context is as simple as adding a file.
|
|
58
|
+
|
|
59
|
+
Like the doctrine, each file has a shared form (`~/.iapeer/<DOMAIN>.md`) and a personal one (`<peer-folder>/.iapeer/<DOMAIN>.md`), inserted general to specific. Only the root of `.iapeer/` is scanned — not subfolders. Files come in a deterministic order (alphabetical by name), each as its own marked section, and empty files drop out. Plugins use this same channel to add their settings (a `<DOMAIN>.md` per plugin); your hand-written files and a plugin's sit side by side.
|
|
60
|
+
|
|
61
|
+
### Layer 5 — doctrine fragments
|
|
62
|
+
|
|
63
|
+
The last layer — fragments that primitive plugins generate by machine. They live in the `fragments/` subfolder of both roots (`~/.iapeer/fragments/` and `<peer-folder>/.iapeer/fragments/`). For example, the memory provider places the agent's current memory map here.
|
|
64
|
+
|
|
65
|
+
This layer is the most volatile, so it goes last and is **re-read on every fresh session bring-up**. When an agent's memory or other machine context changes, the updated fragment reaches the prompt on the next launch.
|
|
66
|
+
|
|
67
|
+
## Assembly and in-session immutability
|
|
68
|
+
|
|
69
|
+
The layers are joined into one file with a blank line between sections. That file is what's passed to the runtime via the prompt-swap flag.
|
|
70
|
+
|
|
71
|
+
The system prompt is fixed at the moment the session comes up and doesn't change while the session lives. When the daemon brings the agent up again — fresh or resuming the prior conversation — it reassembles the file, so the fresh layer-5 fragments and the current date are pulled in on each bring-up. This is verified: resuming the conversation re-applies the new prompt file rather than carrying the old one.
|
|
72
|
+
|
|
73
|
+
The swap delivers iapeer's system prompt — the peer's identity. It doesn't disable the runtime's own native instruction files: a `CLAUDE.md` (Claude) or `AGENTS.md` (Codex) still works exactly as it does outside iapeer — read as standing instructions the model follows. These are two independent channels — iapeer's identity through the swap, the runtime's `CLAUDE.md`/`AGENTS.md` natively — and they apply together. So if you bring an existing project that already has a `CLAUDE.md`, it keeps working alongside the iapeer prompt; those files also work on their own, with no iapeer at all.
|
|
74
|
+
|
|
75
|
+
## What you fill in
|
|
76
|
+
|
|
77
|
+
Of all the layers, two files are filled by hand:
|
|
78
|
+
|
|
79
|
+
- `~/.iapeer/IAPEER.md` — the team's shared rules (once per host);
|
|
80
|
+
- `<peer-folder>/.iapeer/IAPEER.md` — each peer's role.
|
|
81
|
+
|
|
82
|
+
The rest — the identity block, the registry, the fragments — the system and plugins assemble themselves. The directory layout where these files live is covered in [06 — Storage](06-storage.md).
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# 06 — Storage
|
|
2
|
+
|
|
3
|
+
[Русский](ru/06-хранение.md) · **English**
|
|
4
|
+
|
|
5
|
+
iapeer state lives in two places: the shared host directory `~/.iapeer/` and the `.iapeer/` directory inside each peer's working folder.
|
|
6
|
+
|
|
7
|
+
## Two storage levels
|
|
8
|
+
|
|
9
|
+
**The shared level** `~/.iapeer/` — everything concerning the host as a whole: the peer registry, daemon state, shared settings, installed runtime packages.
|
|
10
|
+
|
|
11
|
+
**The peer level** `<peer-folder>/.iapeer/` — everything concerning one peer: its profile, doctrine, its plugins' state, its sessions' logs.
|
|
12
|
+
|
|
13
|
+
The same plugin can store data at both levels: shared in `~/.iapeer/`, peer-bound in the peer's folder.
|
|
14
|
+
|
|
15
|
+
## The shared-level layout
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
~/.iapeer/
|
|
19
|
+
├── IAPEER.md host doctrine (shared by all peers; prompt layer 2)
|
|
20
|
+
├── peers-profiles.json registry of all peers (written under a lock)
|
|
21
|
+
├── peers-profiles.lock registry lock file
|
|
22
|
+
├── memory-provider.json memory slot: who the provider is (if claimed)
|
|
23
|
+
├── fragments/ shared doctrine fragments (prompt layer 5)
|
|
24
|
+
├── peers/
|
|
25
|
+
│ └── <name>/ a peer's default working folder
|
|
26
|
+
│ └── .iapeer/ (peer-level layout — see below)
|
|
27
|
+
├── state/
|
|
28
|
+
│ ├── iapeer/ daemon state (router.sock, router.json …)
|
|
29
|
+
│ └── <plugin>/ a plugin's mutable state
|
|
30
|
+
├── logs/
|
|
31
|
+
│ ├── iapeer/ daemon logs: lifecycle.log, delivery.log, exits.log
|
|
32
|
+
│ └── <name>/ an infrastructure peer's logs (by name)
|
|
33
|
+
├── cache/
|
|
34
|
+
│ ├── iapeer/ core cache
|
|
35
|
+
│ └── <plugin>/ plugin cache (safe to delete)
|
|
36
|
+
├── plugins/
|
|
37
|
+
│ ├── iap/ the core IAP plugin's install
|
|
38
|
+
│ └── <plugin>/ a plugin's install (immutable)
|
|
39
|
+
└── runtimes/
|
|
40
|
+
├── claude/
|
|
41
|
+
│ └── plugins/<plugin>/ a plugin's config under the claude runtime
|
|
42
|
+
├── codex/
|
|
43
|
+
│ └── plugins/<plugin>/
|
|
44
|
+
├── telegram/
|
|
45
|
+
│ └── runtime.json a runtime package's manifest
|
|
46
|
+
└── notifier/
|
|
47
|
+
└── runtime.json
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
A few nodes worth highlighting:
|
|
51
|
+
|
|
52
|
+
- **`peers-profiles.json`** — the registry. Written only through a single writer under the file lock `peers-profiles.lock`; direct editing is forbidden at the core level.
|
|
53
|
+
- **`state/iapeer/`** — where the daemon lives: its local socket `router.sock` and config `router.json`.
|
|
54
|
+
- **`logs/<name>/`** — an infrastructure peer's logs are placed by peer name at the shared level (not in the peer's folder), because it's the output of a launchd service.
|
|
55
|
+
- **`runtimes/<runtime>/`** — dual ownership: the root belongs to the runtime itself (Telegram keeps bot tokens and poll state here), while the `plugins/<plugin>/` subfolder is the plugins' space, holding their config under this runtime.
|
|
56
|
+
|
|
57
|
+
## The peer-level layout
|
|
58
|
+
|
|
59
|
+
```text
|
|
60
|
+
<peer-folder>/.iapeer/
|
|
61
|
+
├── peer-profile.json the peer profile — the source of truth about it
|
|
62
|
+
├── IAPEER.md personal doctrine (prompt layer 2)
|
|
63
|
+
├── fragments/ personal doctrine fragments (layer 5)
|
|
64
|
+
├── plugins/
|
|
65
|
+
│ ├── iap/
|
|
66
|
+
│ └── <plugin>/ a plugin's data for this peer
|
|
67
|
+
├── state/
|
|
68
|
+
│ ├── iap/
|
|
69
|
+
│ └── <plugin>/
|
|
70
|
+
├── logs/
|
|
71
|
+
│ ├── iap/
|
|
72
|
+
│ └── <plugin>/
|
|
73
|
+
├── cache/
|
|
74
|
+
│ ├── iap/
|
|
75
|
+
│ └── <plugin>/
|
|
76
|
+
└── runtimes/
|
|
77
|
+
├── claude/
|
|
78
|
+
│ ├── launch.env launch args and env vars for this runtime
|
|
79
|
+
│ └── plugins/<plugin>/
|
|
80
|
+
└── codex/
|
|
81
|
+
├── launch.env
|
|
82
|
+
└── plugins/<plugin>/
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
- **`peer-profile.json`** — the peer's card and the single source of truth about it (the fields are covered in [03 — Peers, runtimes, identity](03-peers-runtimes-identity.md)).
|
|
86
|
+
- **`launch.env`** — a `KEY=VALUE` file for a specific peer+runtime pair: extra launch arguments (`PEER_START_ARGS`) and environment variables. So one peer can be given special launch flags without touching the rest.
|
|
87
|
+
|
|
88
|
+
A peer's working folder doesn't have to live under `~/.iapeer/peers/`. By default `iapeer create` puts it there, but any folder can be made a peer with `iapeer init` — then `.iapeer/` appears right inside it.
|
|
89
|
+
|
|
90
|
+
## Who owns what
|
|
91
|
+
|
|
92
|
+
The ownership boundary is simple: everyone owns the contents of their own folder and stays out of others'.
|
|
93
|
+
|
|
94
|
+
| Owner | What belongs to it |
|
|
95
|
+
|---|---|
|
|
96
|
+
| **Core (`iapeer`)** | `peers-profiles.json`, `state/iapeer/`, `logs/iapeer/`, `cache/iapeer/` |
|
|
97
|
+
| **Runtime** | the root of `runtimes/<runtime>/` — except the `plugins/` subfolder |
|
|
98
|
+
| **Plugin** | its own `<plugin>/` folder in every category (`state`, `logs`, `cache`, `plugins`) at both levels |
|
|
99
|
+
|
|
100
|
+
The name `iapeer` in the categories is reserved for the core. A runtime never touches `plugins/<plugin>/` inside its space, and a plugin never touches the runtime root.
|
|
101
|
+
|
|
102
|
+
## Data classes by lifetime
|
|
103
|
+
|
|
104
|
+
Install is separated from mutable data — important for updates and for cleanup:
|
|
105
|
+
|
|
106
|
+
- **Install** (`plugins/<plugin>/`) — immutable. It's the plugin's unpacked code; an update replaces it whole.
|
|
107
|
+
- **State** (`state/`) — mutable data worth protecting: losing it is losing work.
|
|
108
|
+
- **Logs** (`logs/`) — process output; useful for analysis but not critical. What's written to `lifecycle.log`, `delivery.log`, `exits.log` and how to configure their rotation — [14 — Configuration and logs](14-configuration-and-logs.md).
|
|
109
|
+
- **Cache** (`cache/`) — recoverable data; it can be deleted with no consequence, the system rebuilds it.
|
|
110
|
+
|
|
111
|
+
This split means updating a plugin is replacing its install folder without touching its state; and freeing space is deleting the cache without risking data.
|
|
112
|
+
|
|
113
|
+
## The registry as a derivative
|
|
114
|
+
|
|
115
|
+
Worth repeating the key storage principle: **the registry `peers-profiles.json` is a projection of the local profiles, not a separate source of truth.** The source of truth about a peer is its `peer-profile.json`. The registry can be reprojected from the profiles at any time with `iapeer verify --fix`. On a divergence between the registry and a profile, the profile wins. So losing or corrupting the registry isn't fatal — it's rebuilt by walking the peers' working folders.
|