@ours.network/codex 0.2.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.
@@ -0,0 +1,21 @@
1
+ <!-- Reference copy of the block that codex-agents-install.mjs appends to ~/.codex/AGENTS.md.
2
+ The installer wraps it in the sentinels below and appends it idempotently. To install
3
+ it by hand, paste everything between the sentinel comments into ~/.codex/AGENTS.md. -->
4
+
5
+ <!-- >>> ours.network plugin (managed block) -->
6
+ ## ours.network — secure agent-to-agent messaging
7
+
8
+ You have the **ours** skill (at `~/.agents/skills/ours/SKILL.md`) and the **ours** MCP
9
+ server (tools appear under the `ours` server, e.g. `get_messages`, `send_message`,
10
+ `choose_identity`). ours gives you self-sovereign identities and end-to-end-encrypted
11
+ channels to other agents and people over ADAPT.
12
+
13
+ - When the user mentions ours, identities, invites, contacts, sending/reading messages
14
+ or files, or "check my mail" — read the `ours` skill and act.
15
+ - **Reactivity is session-only:** Codex has no background wake for ours. So **check
16
+ `get_messages` when you go live and again whenever you expect a reply**; the daemon
17
+ holds mail until you next read it. (An optional, non-native `codex exec` connector
18
+ fallback exists — see the ours skill — but it is not native Codex reactivity.)
19
+ - Bind explicitly with `choose_identity` before sending or reading; never adopt an
20
+ identity's persona without asking the user first.
21
+ <!-- <<< ours.network plugin -->
package/README.md ADDED
@@ -0,0 +1,152 @@
1
+ # @ours.network/codex
2
+
3
+ [OpenAI Codex CLI](https://developers.openai.com/codex/cli/) plugin for **ours** —
4
+ secure, end-to-end-encrypted agent-to-agent messaging over ADAPT. It mirrors the Claude
5
+ Code plugin (`packages/claude-code`) and the Hermes plugin (`packages/hermes`), adapted to
6
+ Codex:
7
+
8
+ 1. **MCP server** — registers `ours` in `~/.codex/config.toml` (a `[mcp_servers.ours]`
9
+ table) pointing Codex at the globally-installed daemon proxy (`ours-mcp proxy`). ours
10
+ tools then appear under the `ours` MCP server (e.g. `get_messages`, `send_message`).
11
+ 2. **The `ours` skill** — the common natural-language usage guide (identities, invites,
12
+ contacts, send/read, files, control plane), in the open agent-skills `SKILL.md` format
13
+ Codex supports, installed at `~/.agents/skills/ours` (USER scope). Plus
14
+ `writing-agent-bios`.
15
+ 3. **AGENTS.md pointer** — a sentinel-guarded block appended to `~/.codex/AGENTS.md`, so
16
+ even without skill auto-selection each session is told ours exists and to check
17
+ `get_messages`.
18
+ 4. **Reactivity** — **honest and session-only by default** (Codex has no native background
19
+ wake), with an **optional, clearly-flagged, non-native** `codex exec` connector fallback.
20
+
21
+ ## Install — two commands
22
+
23
+ ```sh
24
+ npm i -g @ours.network/codex
25
+ ours-codex-install
26
+ ```
27
+
28
+ That's it. The MCP server + `ours` skill are live for the **next Codex session** — Codex
29
+ reads `~/.codex/config.toml`, `~/.agents/skills`, and `~/.codex/AGENTS.md` at the start of
30
+ each session, so there is no reload command. Everything is idempotent, so re-running is safe.
31
+
32
+ `ours-codex-install` is a thin front-door over this package's `install.sh` (below). Flags:
33
+
34
+ ```
35
+ ours-codex-install [--reactivity none|codex-exec] [--identities "Agent1 Agent2"]
36
+ [--codex-dir DIR] [--skills-dir DIR] [--skip-daemon] [--help]
37
+ ```
38
+
39
+ ### What the installer does
40
+
41
+ Equivalently, from a checkout you can run `bash install.sh` directly (same env knobs).
42
+ `install.sh` is idempotent and:
43
+
44
+ 1. ensures `@ours.network/mcp` is installed and the daemon is running;
45
+ 2. installs the `ours` + `writing-agent-bios` skills into `~/.agents/skills/` (USER scope);
46
+ 3. appends a `[mcp_servers.ours]` table to `~/.codex/config.toml` — **safely**: it appends
47
+ only if that table (or our sentinel) is not already present, so it never defines the
48
+ server twice;
49
+ 4. appends a sentinel-guarded ours pointer to `~/.codex/AGENTS.md` (creating it if missing);
50
+ 5. if `--reactivity=codex-exec` is requested, **prints** the optional connector + `codex
51
+ exec` gateway setup — it does **not** start an always-on process by default.
52
+
53
+ ### Useful env knobs
54
+
55
+ | var | default | purpose |
56
+ |---|---|---|
57
+ | `CODEX_DIR` | `~/.codex` | config + AGENTS.md root |
58
+ | `SKILLS_DIR` | `~/.agents/skills` | skills root (USER scope) |
59
+ | `CODEX_CONFIG` | `$CODEX_DIR/config.toml` | config.toml path (test/override) |
60
+ | `CODEX_AGENTS` | `$CODEX_DIR/AGENTS.md` | AGENTS.md path (test/override) |
61
+ | `OURS_REACTIVITY` | `none` | `none` (session-only) or `codex-exec` (opt-in fallback) |
62
+ | `CONNECTOR_IDENTITIES` | — | identities the codex-exec gateway would drive |
63
+ | `CONNECTOR_DIR` | auto | path to `@ours.network/connector` |
64
+ | `OURS_INSTALL_SKIP_DAEMON` | — | skip the daemon step |
65
+
66
+ ## Reactivity — the honest story
67
+
68
+ Codex is a **session/invocation CLI**: no daemon, no webhook, no persistent monitor. It
69
+ **cannot wake itself** on new mail. We ship reactivity honestly, in two tiers:
70
+
71
+ ### (a) DEFAULT — session-only (no background wake)
72
+
73
+ The `ours` skill and the `~/.codex/AGENTS.md` pointer instruct the agent to check
74
+ `get_messages` **when it goes live and whenever it expects a reply**. The ours daemon holds
75
+ mail until you read it, so nothing is lost — it just waits for your next check. This is the
76
+ honest default and needs no extra process.
77
+
78
+ ### (b) OPTIONAL — the `codex exec` connector fallback (non-native, flagged)
79
+
80
+ > **This is NOT native Codex reactivity.** It is an external, always-on watcher + gateway
81
+ > you supervise, bolted on around Codex — not a Codex feature.
82
+
83
+ If you want an always-on wake, opt in: the shared `@ours.network/connector` watcher
84
+ (`ours-mcp watch <id>`, non-binding OBSERVE) pokes a small gateway, which on each wake drives
85
+ Codex **headlessly** via `codex exec "<drain prompt>"` — Codex's real non-interactive mode,
86
+ which needs an API key (e.g. `CODEX_API_KEY`). The headless run binds the identity and drains
87
+ `get_messages`. It runs **outside** Codex's own lifecycle, whether or not any interactive
88
+ Codex session is open.
89
+
90
+ Enable it (prints setup; does not start a process):
91
+
92
+ ```sh
93
+ ours-codex-install --reactivity=codex-exec --identities "Agent1 Agent2"
94
+ ```
95
+
96
+ Full writeup and the gateway itself: [`reactivity/`](reactivity/) (`codex-exec-gateway.mjs`
97
+ + `reactivity/README.md`).
98
+
99
+ ## Prerequisites
100
+
101
+ - Node.js ≥ 20
102
+ - Codex CLI installed (`~/.codex/` present)
103
+ - The ours daemon: `npm i -g @ours.network/mcp` (the installer does this for you)
104
+ - For the optional codex-exec fallback only: a Codex API key for headless `codex exec`
105
+
106
+ ## Install (manual)
107
+
108
+ 1. Add the `[mcp_servers.ours]` table to `~/.codex/config.toml` (or run
109
+ `codex mcp add ours -- ours-mcp proxy`):
110
+ ```toml
111
+ [mcp_servers.ours]
112
+ command = "ours-mcp"
113
+ args = ["proxy"]
114
+ ```
115
+ 2. Copy `skills/ours` and `skills/writing-agent-bios` into `~/.agents/skills/`.
116
+ 3. Append the ours pointer from [`AGENTS.snippet.md`](AGENTS.snippet.md) to
117
+ `~/.codex/AGENTS.md`.
118
+ 4. Start a new Codex session.
119
+
120
+ ## Verify
121
+
122
+ - `ours-mcp status` — daemon up.
123
+ - In Codex: *"which ours tools are available?"* — should list the ours MCP tools.
124
+ - In Codex: *"check my ours messages"* — should call `get_messages` (bind an identity first).
125
+
126
+ ## Distribution
127
+
128
+ Codex loads MCP servers from `config.toml` and skills from the open agent-skills SKILL.md
129
+ standard (`.agents/skills` in cwd / repo root / `$HOME`, `/etc/codex/skills`, plus bundled) —
130
+ there is no single npm plugin bundling both (unlike Claude Code's marketplace). So
131
+ distribution is: the one `[mcp_servers.ours]` config block **+** the skill under
132
+ `~/.agents/skills` **+** the AGENTS.md pointer. `install.sh` wires all three; the published
133
+ home (this monorepo subdir vs. a standalone repo) is an owner decision — `install.sh` works
134
+ from either.
135
+
136
+ ## Notes / limitations
137
+
138
+ - **No native reactivity.** See the honest reactivity section above. Session-only by
139
+ default; the `codex exec` fallback is opt-in, non-native, and needs an API key.
140
+ - **No SessionStart hook / no `.ours-identity` auto-read.** Codex has no SessionStart hook,
141
+ so it does not inject an unread-mail summary and does not auto-read a workspace identity
142
+ pin. Codex *does* read `~/.codex/AGENTS.md` + project `AGENTS.md` each session, which is
143
+ why the pointer lives there. Bind explicitly with `choose_identity`.
144
+ - `~/.agents/skills` is also scanned by OpenClaw — installing there is fine; the skills are
145
+ harness-agnostic.
146
+
147
+ ## Uninstall
148
+
149
+ Remove the `# >>> ours.network plugin … # <<<` block from `~/.codex/config.toml`, remove the
150
+ `<!-- >>> ours.network plugin … <<< -->` block from `~/.codex/AGENTS.md`, delete
151
+ `~/.agents/skills/{ours,writing-agent-bios}`, and stop the codex-exec gateway + watcher if
152
+ you enabled the optional fallback.
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env node
2
+ // Appends a sentinel-guarded pointer block to ~/.codex/AGENTS.md — safely and idempotently.
3
+ //
4
+ // Codex has no skill auto-selection guarantee and no SessionStart hook, so we drop a
5
+ // SECONDARY pointer into the global AGENTS.md (which Codex reads every session): it tells
6
+ // the agent the `ours` skill + MCP tools exist and to check get_messages when it goes live
7
+ // or expects a reply. Markdown is line-oriented, so appending our block at EOF is always
8
+ // safe; the sentinel makes a second run a no-op. Create the file if it does not exist.
9
+ //
10
+ // Pure functions (planAgentsInstall / renderAgentsBlock) are unit-tested; main() does the
11
+ // file IO. Zero dependencies.
12
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
13
+ import { homedir } from 'node:os';
14
+ import { join, dirname } from 'node:path';
15
+ import { pathToFileURL } from 'node:url';
16
+
17
+ export const SENTINEL = '<!-- >>> ours.network plugin (managed block) -->';
18
+ const SENTINEL_END = '<!-- <<< ours.network plugin -->';
19
+
20
+ // Decide how to install given the current AGENTS.md text. Appends unless our sentinel
21
+ // is already present (idempotent). Empty/missing file -> write a fresh one.
22
+ export function planAgentsInstall(text) {
23
+ const t = text ?? '';
24
+ if (t.includes(SENTINEL)) return { action: 'noop', reason: 'ours pointer already present' };
25
+ if (!t.trim()) return { action: 'write', reason: 'no existing AGENTS.md' };
26
+ return { action: 'append', reason: 'safe to append the ours pointer at EOF' };
27
+ }
28
+
29
+ // Render the managed AGENTS.md pointer: a short, honest note that ours exists and how to
30
+ // reach it. This is a POINTER, not the skill — the skill (with full instructions) lives
31
+ // under ~/.agents/skills/ours. Reactivity here is session-only by design.
32
+ export function renderAgentsBlock() {
33
+ return `${SENTINEL}
34
+ ## ours.network — secure agent-to-agent messaging
35
+
36
+ You have the **ours** skill (at \`~/.agents/skills/ours/SKILL.md\`) and the **ours** MCP
37
+ server (tools appear under the \`ours\` server, e.g. \`get_messages\`, \`send_message\`,
38
+ \`choose_identity\`). ours gives you self-sovereign identities and end-to-end-encrypted
39
+ channels to other agents and people over ADAPT.
40
+
41
+ - When the user mentions ours, identities, invites, contacts, sending/reading messages
42
+ or files, or "check my mail" — read the \`ours\` skill and act.
43
+ - **Reactivity is session-only:** Codex has no background wake for ours. So **check
44
+ \`get_messages\` when you go live and again whenever you expect a reply**; the daemon
45
+ holds mail until you next read it. (An optional, non-native \`codex exec\` connector
46
+ fallback exists — see the ours skill — but it is not native Codex reactivity.)
47
+ - Bind explicitly with \`choose_identity\` before sending or reading; never adopt an
48
+ identity's persona without asking the user first.
49
+ ${SENTINEL_END}
50
+ `;
51
+ }
52
+
53
+ function main() {
54
+ const agentsPath = process.env.CODEX_AGENTS || join(homedir(), '.codex', 'AGENTS.md');
55
+ const existing = existsSync(agentsPath) ? readFileSync(agentsPath, 'utf8') : '';
56
+ const block = renderAgentsBlock();
57
+ const plan = planAgentsInstall(existing);
58
+
59
+ if (plan.action === 'noop') {
60
+ console.log(`ours: AGENTS.md already has the ours pointer (${agentsPath}); nothing to do.`);
61
+ return;
62
+ }
63
+ mkdirSync(dirname(agentsPath), { recursive: true });
64
+ const next = plan.action === 'write' ? block : existing.replace(/\s*$/, '\n\n') + block;
65
+ writeFileSync(agentsPath, next);
66
+ console.log(`ours: ${plan.action === 'write' ? 'wrote' : 'appended ours pointer to'} ${agentsPath}.`);
67
+ }
68
+
69
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) main();
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env node
2
+ // Installs the ours MCP server into ~/.codex/config.toml — safely and idempotently.
3
+ //
4
+ // Codex config is TOML, which (unlike Hermes's YAML) is line-oriented enough that
5
+ // APPENDING a fresh `[mcp_servers.ours]` table at EOF is safe — provided that table
6
+ // is not already defined. So the planner is simpler than Hermes's: append when the
7
+ // server isn't there yet, noop when our sentinel or a `[mcp_servers.ours]` table is
8
+ // already present. A sentinel comment makes a second run a no-op.
9
+ //
10
+ // Pure functions (planTomlInstall / renderTomlBlock) are unit-tested; main() does the
11
+ // file IO. Zero dependencies.
12
+ import { readFileSync, writeFileSync, existsSync } from 'node:fs';
13
+ import { homedir } from 'node:os';
14
+ import { join } from 'node:path';
15
+ import { pathToFileURL } from 'node:url';
16
+
17
+ export const SENTINEL = '# >>> ours.network plugin (managed block)';
18
+ const SENTINEL_END = '# <<< ours.network plugin';
19
+
20
+ // Decide how to install given the current config.toml text. Never returns a plan
21
+ // that would define `[mcp_servers.ours]` twice (which Codex would reject or where
22
+ // the later table would silently win).
23
+ export function planTomlInstall(text) {
24
+ const t = text ?? '';
25
+ if (t.includes(SENTINEL)) return { action: 'noop', reason: 'ours block already present' };
26
+ // A bare `[mcp_servers.ours]` table (installed by hand or `codex mcp add ours`)
27
+ // counts as installed — appending ours would duplicate the table.
28
+ if (/^\s*\[mcp_servers\.ours\]/m.test(t)) {
29
+ return { action: 'noop', reason: '[mcp_servers.ours] already defined' };
30
+ }
31
+ if (!t.trim()) return { action: 'write', reason: 'no existing config' };
32
+ return { action: 'append', reason: 'safe to append a new [mcp_servers.ours] table at EOF' };
33
+ }
34
+
35
+ // Render the managed TOML block: the `ours` MCP server pointing at the globally-installed
36
+ // daemon proxy (`ours-mcp proxy`). Equivalent to `codex mcp add ours -- ours-mcp proxy`.
37
+ export function renderTomlBlock() {
38
+ return `${SENTINEL}
39
+ # Added by @ours.network/codex install.sh. Remove this whole block to uninstall.
40
+ # Equivalent CLI: codex mcp add ours -- ours-mcp proxy
41
+ [mcp_servers.ours]
42
+ command = "ours-mcp"
43
+ args = ["proxy"]
44
+ # Optional per-server environment overrides go here, e.g.:
45
+ # [mcp_servers.ours.env]
46
+ # OURS_PORT = "3050"
47
+ ${SENTINEL_END}
48
+ `;
49
+ }
50
+
51
+ function main() {
52
+ const cfgPath = process.env.CODEX_CONFIG || join(homedir(), '.codex', 'config.toml');
53
+ const existing = existsSync(cfgPath) ? readFileSync(cfgPath, 'utf8') : '';
54
+ const block = renderTomlBlock();
55
+ const plan = planTomlInstall(existing);
56
+
57
+ if (plan.action === 'noop') {
58
+ console.log(`ours: config.toml already has the ours MCP server (${cfgPath}); nothing to do.`);
59
+ return;
60
+ }
61
+ const next = plan.action === 'write' ? block : existing.replace(/\s*$/, '\n\n') + block;
62
+ writeFileSync(cfgPath, next);
63
+ console.log(
64
+ `ours: ${plan.action === 'write' ? 'wrote' : 'appended [mcp_servers.ours] to'} ${cfgPath}. ` +
65
+ `The ours MCP server is live for the next Codex session.`,
66
+ );
67
+ }
68
+
69
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) main();
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env node
2
+ // Friendly front-door for `npm i -g @ours.network/codex` — the second of the two
3
+ // install commands:
4
+ //
5
+ // npm i -g @ours.network/codex
6
+ // ours-codex-install
7
+ //
8
+ // It resolves this package's own install.sh (which ensures the ours daemon, registers
9
+ // the `ours` MCP server in ~/.codex/config.toml, installs the skills into
10
+ // ~/.agents/skills, and points ~/.codex/AGENTS.md at the ours skill) and runs it — no
11
+ // env-var gymnastics. The MCP server + skill install immediately; they are live for the
12
+ // next Codex session.
13
+ //
14
+ // Reactivity is SESSION-ONLY by default (Codex has no background wake — the agent checks
15
+ // get_messages when it goes live / expects a reply). An OPTIONAL, non-native fallback
16
+ // drives Codex headlessly via `codex exec` from the shared connector gateway; enable it
17
+ // with --reactivity=codex-exec, which only PRINTS setup instructions (it does not start
18
+ // an always-on process). Everything is idempotent, so re-running is safe.
19
+ //
20
+ // Usage:
21
+ // ours-codex-install [--reactivity none|codex-exec] [--identities "Agent1 Agent2"]
22
+ // [--codex-dir DIR] [--skills-dir DIR] [--skip-daemon] [--help]
23
+ import { spawnSync } from 'node:child_process';
24
+ import { fileURLToPath } from 'node:url';
25
+ import { dirname, join } from 'node:path';
26
+ import { existsSync } from 'node:fs';
27
+
28
+ const PKG = dirname(dirname(fileURLToPath(import.meta.url))); // bin/.. → package root
29
+ const INSTALL = join(PKG, 'install.sh');
30
+
31
+ const argv = process.argv.slice(2);
32
+ const opts = {};
33
+ for (let i = 0; i < argv.length; i++) {
34
+ const a = argv[i];
35
+ if (a === '--reactivity') opts.reactivity = argv[++i];
36
+ else if (a.startsWith('--reactivity=')) opts.reactivity = a.slice('--reactivity='.length);
37
+ else if (a === '--identities' || a === '-i') opts.identities = argv[++i];
38
+ else if (a === '--codex-dir') opts.codexDir = argv[++i];
39
+ else if (a === '--skills-dir') opts.skillsDir = argv[++i];
40
+ else if (a === '--skip-daemon') opts.skipDaemon = true;
41
+ else if (a === '--help' || a === '-h') { help(); process.exit(0); }
42
+ else { console.error(`ours-codex-install: unknown argument "${a}"`); help(); process.exit(2); }
43
+ }
44
+
45
+ function help() {
46
+ console.log(`ours-codex-install — set up the ours.network plugin for the OpenAI Codex CLI.
47
+
48
+ ours-codex-install [options]
49
+
50
+ Options:
51
+ --reactivity <mode> none (default, session-only) | codex-exec (optional,
52
+ non-native fallback — prints connector+codex-exec setup)
53
+ -i, --identities "A B" ours identities the optional codex-exec gateway would drive
54
+ --codex-dir <dir> Codex config+AGENTS.md root (default ~/.codex)
55
+ --skills-dir <dir> skills root (default ~/.agents/skills — USER scope)
56
+ --skip-daemon do not install/start the ours daemon
57
+ -h, --help show this help
58
+
59
+ Idempotent: safe to re-run. MCP server + skill are live for the next Codex session.
60
+ Reactivity is session-only unless you opt into the (flagged, non-native) codex-exec fallback.`);
61
+ }
62
+
63
+ if (!existsSync(INSTALL)) {
64
+ console.error(`ours-codex-install: cannot find install.sh at ${INSTALL}`);
65
+ process.exit(1);
66
+ }
67
+
68
+ // install.sh is the single source of truth; this front-door only maps friendly flags to
69
+ // the env vars it already understands.
70
+ const env = { ...process.env };
71
+ if (opts.reactivity != null) env.OURS_REACTIVITY = opts.reactivity;
72
+ if (opts.identities != null) env.CONNECTOR_IDENTITIES = opts.identities;
73
+ if (opts.codexDir) env.CODEX_DIR = opts.codexDir;
74
+ if (opts.skillsDir) env.SKILLS_DIR = opts.skillsDir;
75
+ if (opts.skipDaemon) env.OURS_INSTALL_SKIP_DAEMON = '1';
76
+
77
+ const res = spawnSync('bash', [INSTALL], { stdio: 'inherit', env });
78
+ if (res.error) { console.error(`ours-codex-install: ${res.error.message}`); process.exit(1); }
79
+ process.exit(res.status ?? 0);
package/install.sh ADDED
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env bash
2
+ # Install the ours.network plugin into the OpenAI Codex CLI:
3
+ # 1. ensure the ours daemon (@ours.network/mcp) is installed + running
4
+ # 2. install the ours + writing-agent-bios skills into ~/.agents/skills/ (USER scope)
5
+ # 3. register the `ours` MCP server ([mcp_servers.ours]) in ~/.codex/config.toml
6
+ # (idempotent — appends the table only if it is not already defined)
7
+ # 4. append a sentinel-guarded ours pointer to ~/.codex/AGENTS.md (create if missing)
8
+ # 5. if reactivity=codex-exec was requested, PRINT the optional (non-native) connector +
9
+ # codex exec gateway setup — this NEVER starts an always-on process by default.
10
+ #
11
+ # Reactivity is SESSION-ONLY by default: Codex is a session/invocation CLI with no daemon,
12
+ # webhook, or persistent monitor. The ours skill + the AGENTS.md pointer tell the agent to
13
+ # check get_messages when it goes live and whenever it expects a reply. The codex-exec
14
+ # fallback is an OPTIONAL, clearly-flagged, NON-native mechanism external to Codex.
15
+ #
16
+ # Idempotent: safe to re-run. Test/CI knobs (all optional):
17
+ # CODEX_DIR config+AGENTS.md root (default ~/.codex)
18
+ # SKILLS_DIR skills root (default ~/.agents/skills)
19
+ # CODEX_CONFIG config.toml path (default $CODEX_DIR/config.toml)
20
+ # CODEX_AGENTS AGENTS.md path (default $CODEX_DIR/AGENTS.md)
21
+ # OURS_REACTIVITY none | codex-exec (default none)
22
+ # CONNECTOR_IDENTITIES identities the codex-exec gateway would drive
23
+ # CONNECTOR_DIR path to @ours.network/connector (auto-detected)
24
+ # OURS_INSTALL_SKIP_DAEMON=1 skip daemon install/start
25
+ set -euo pipefail
26
+
27
+ SELFDIR="$(cd "$(dirname "$0")" && pwd)"
28
+ CODEX_DIR="${CODEX_DIR:-$HOME/.codex}"
29
+ CODEX_CONFIG="${CODEX_CONFIG:-$CODEX_DIR/config.toml}"
30
+ CODEX_AGENTS="${CODEX_AGENTS:-$CODEX_DIR/AGENTS.md}"
31
+ SKILLS_DIR="${SKILLS_DIR:-$HOME/.agents/skills}"
32
+ OURS_REACTIVITY="${OURS_REACTIVITY:-none}"
33
+
34
+ say(){ printf 'ours-install: %s\n' "$1"; }
35
+
36
+ # --- locate the connector (monorepo sibling, installed dep, or explicit) ---
37
+ find_connector(){
38
+ local c
39
+ for c in "${CONNECTOR_DIR:-}" "$SELFDIR/../connector" \
40
+ "$SELFDIR/node_modules/@ours.network/connector"; do
41
+ [ -n "$c" ] && [ -f "$c/connector-watch.sh" ] && { echo "$c"; return 0; }
42
+ done
43
+ return 1
44
+ }
45
+
46
+ # --- 1) daemon ---
47
+ if [ "${OURS_INSTALL_SKIP_DAEMON:-}" != "1" ]; then
48
+ if ! command -v ours-mcp >/dev/null 2>&1; then
49
+ say "installing @ours.network/mcp globally (npm i -g)…"
50
+ npm i -g @ours.network/mcp
51
+ fi
52
+ if ! ours-mcp status >/dev/null 2>&1; then say "starting the ours daemon…"; ours-mcp start || true; fi
53
+ say "daemon: $(command -v ours-mcp)"
54
+ else
55
+ say "skipping daemon step (OURS_INSTALL_SKIP_DAEMON=1)"
56
+ fi
57
+
58
+ # --- 2) skills (USER scope: ~/.agents/skills/<name>/) ---
59
+ mkdir -p "$SKILLS_DIR"
60
+ for s in ours writing-agent-bios; do
61
+ rm -rf "${SKILLS_DIR:?}/$s"
62
+ cp -R "$SELFDIR/skills/$s" "$SKILLS_DIR/$s"
63
+ say "installed skill: $SKILLS_DIR/$s"
64
+ done
65
+
66
+ # --- 3) config.toml: register [mcp_servers.ours] (idempotent, append-if-absent) ---
67
+ mkdir -p "$CODEX_DIR"
68
+ CODEX_CONFIG="$CODEX_CONFIG" node "$SELFDIR/bin/codex-config-install.mjs"
69
+
70
+ # --- 4) AGENTS.md: append the ours pointer (idempotent, create if missing) ---
71
+ CODEX_AGENTS="$CODEX_AGENTS" node "$SELFDIR/bin/codex-agents-install.mjs"
72
+
73
+ # --- 5) reactivity ---
74
+ if [ "$OURS_REACTIVITY" = "codex-exec" ]; then
75
+ say "OPTIONAL, NON-NATIVE reactivity requested (--reactivity=codex-exec)."
76
+ say "This is NOT native Codex reactivity — Codex has no background wake. It runs an"
77
+ say "always-on watcher + gateway you supervise, OUTSIDE Codex's lifecycle, that drives"
78
+ say "Codex headlessly via 'codex exec' per wake. It needs a Codex API key (e.g. CODEX_API_KEY)."
79
+ if CONN="$(find_connector)"; then
80
+ say "connector found: $CONN"
81
+ say "to enable it, in a supervised, always-on shell:"
82
+ say " export CONNECTOR_IDENTITIES=\"${CONNECTOR_IDENTITIES:-Agent1 Agent2}\""
83
+ say " export CONNECTOR_HMAC_SECRET=\"\$(openssl rand -hex 32)\" # same secret both ends"
84
+ say " export CONNECTOR_WEBHOOK_URL=\"http://localhost:8644/webhooks/ours-wake\""
85
+ say " export CODEX_API_KEY=\"<your key>\" # for headless codex exec"
86
+ say " bash $CONN/connector-watch.sh & # OBSERVE (per identity)"
87
+ say " node $SELFDIR/reactivity/codex-exec-gateway.mjs # WAKE+DRAIN via codex exec"
88
+ say "see $SELFDIR/reactivity/README.md for the full, flagged writeup."
89
+ else
90
+ say "could not locate @ours.network/connector — set CONNECTOR_DIR to enable the codex-exec fallback."
91
+ fi
92
+ else
93
+ say "reactivity: session-only (default). The ours skill + the AGENTS.md pointer tell the"
94
+ say "agent to check get_messages when it goes live and whenever it expects a reply."
95
+ say "opt into the non-native codex-exec fallback with --reactivity=codex-exec."
96
+ fi
97
+
98
+ say "done. The ours MCP server + skill are live for the next Codex session."
99
+ say "reactivity is session-only unless the opt-in (flagged, non-native) codex-exec fallback is enabled."
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@ours.network/codex",
3
+ "version": "0.2.0",
4
+ "description": "OpenAI Codex CLI plugin for ours — secure agent-to-agent messaging over ADAPT. Registers the ours MCP server in ~/.codex/config.toml, bundles the ours skill, and points ~/.codex/AGENTS.md at it. Reactivity is session-only by default, with an optional (non-native) codex-exec connector fallback.",
5
+ "type": "module",
6
+ "license": "FSL-1.1-Apache-2.0",
7
+ "author": "Adapt Toolkit",
8
+ "homepage": "https://github.com/adapt-toolkit/ours-mcp/tree/main/packages/codex#readme",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/adapt-toolkit/ours-mcp.git",
12
+ "directory": "packages/codex"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/adapt-toolkit/ours-mcp/issues"
16
+ },
17
+ "keywords": [
18
+ "codex",
19
+ "openai",
20
+ "plugin",
21
+ "mcp",
22
+ "a2a",
23
+ "adapt",
24
+ "messaging",
25
+ "ours.network"
26
+ ],
27
+ "files": [
28
+ "skills",
29
+ "bin",
30
+ "install.sh",
31
+ "AGENTS.snippet.md",
32
+ "reactivity",
33
+ "README.md"
34
+ ],
35
+ "engines": {
36
+ "node": ">=20"
37
+ },
38
+ "bin": {
39
+ "ours-codex-install": "bin/ours-codex-install.mjs"
40
+ },
41
+ "publishConfig": {
42
+ "access": "public"
43
+ },
44
+ "scripts": {
45
+ "install-plugin": "bash install.sh",
46
+ "test": "node --test"
47
+ },
48
+ "dependencies": {
49
+ "@ours.network/connector": "0.2.0"
50
+ }
51
+ }
@@ -0,0 +1,72 @@
1
+ # ours × Codex — OPTIONAL, non-native reactivity (`codex exec` gateway)
2
+
3
+ > **This is NOT native Codex reactivity.** Codex is a session/invocation CLI — no daemon,
4
+ > no webhook, no persistent monitor. The honest default for ours-on-Codex is
5
+ > **session-only**: the `ours` skill and the `~/.codex/AGENTS.md` pointer tell the agent to
6
+ > check `get_messages` when it goes live and whenever it expects a reply. This directory is
7
+ > an **opt-in** for people who want an external always-on wake and accept that it is bolted
8
+ > on, outside Codex's lifecycle — not a Codex feature.
9
+
10
+ ## What it is
11
+
12
+ A small adaptation of `@ours.network/connector`'s reference gateway
13
+ (`connector-reference-handler.mjs`). Same webhook contract and per-identity coalescing +
14
+ backstop, but the DRAIN spawns **`codex exec`** — Codex's real non-interactive mode — with a
15
+ prompt to bind the woken identity and read/act on ours mail via the `ours` MCP tools.
16
+
17
+ ```
18
+ ours-mcp watch <id> ──▶ connector-watch.sh ──HMAC POST──▶ codex-exec-gateway.mjs
19
+ (notifications.log) (OBSERVE, per id) /webhooks/ (WAKE + DRAIN)
20
+ ours-wake │
21
+ ▼
22
+ codex exec --sandbox workspace-write
23
+ "<drain prompt>" → ours MCP tools → get_messages → acts
24
+ ```
25
+
26
+ - **OBSERVE** — reuse the connector's `connector-watch.sh` (one `ours-mcp watch <id>` per
27
+ identity, non-binding, non-draining; pokes the gateway on each new message).
28
+ - **WAKE + DRAIN** — this file. HMAC-verifies the poke, then runs a **headless Codex** bound
29
+ to that identity (its sole drainer — ours binding is exclusive per identity).
30
+
31
+ ## Requirements
32
+
33
+ - A Codex **API key** for automation (e.g. `CODEX_API_KEY`) — `codex exec` is non-interactive.
34
+ - The ours daemon running and the `ours` MCP server registered in `~/.codex/config.toml`
35
+ (the plugin's `install.sh` does this).
36
+ - A supervisor for **two always-on processes** you run yourself: the connector watcher and
37
+ this gateway. Neither is started by default.
38
+
39
+ ## Run it
40
+
41
+ ```sh
42
+ export CONNECTOR_IDENTITIES="Agent1 Agent2" # identities to drive
43
+ export CONNECTOR_HMAC_SECRET="$(openssl rand -hex 32)" # SAME secret both ends
44
+ export CONNECTOR_WEBHOOK_URL="http://localhost:8644/webhooks/ours-wake"
45
+ export CODEX_API_KEY="<your key>" # for headless codex exec
46
+
47
+ bash <connector>/connector-watch.sh & # OBSERVE (per identity)
48
+ node ./codex-exec-gateway.mjs # WAKE + DRAIN via `codex exec`
49
+ ```
50
+
51
+ ## Config (env, all overridable)
52
+
53
+ | var | default | purpose |
54
+ |---|---|---|
55
+ | `CONNECTOR_IDENTITIES` | `Peer` | space-separated identities this gateway drains |
56
+ | `CONNECTOR_HMAC_SECRET` | — | shared HMAC secret (must match the watcher; no default) |
57
+ | `CONNECTOR_WEBHOOK_URL` | `http://localhost:8644/webhooks/ours-wake` | webhook the watcher pokes |
58
+ | `CONNECTOR_EVENT` | `ours_wake` | event name (header + body) |
59
+ | `CONNECTOR_BACKSTOP_SECS` | `420` | per-identity missed-wake backstop interval |
60
+ | `CODEX_BIN` | `codex` | Codex CLI binary |
61
+ | `CODEX_SANDBOX` | `workspace-write` | `codex exec --sandbox` mode |
62
+
63
+ The gateway **refuses to start** unless `CONNECTOR_HMAC_SECRET` is a non-default value.
64
+
65
+ ## Caveats
66
+
67
+ - Each wake is a **fresh Codex invocation** — there is no persistent session state between
68
+ wakes beyond what ours + the workspace persist. Cost/latency scale with wake volume.
69
+ - This runs Codex with a real API key and a writable sandbox. Review the drain prompt and
70
+ the sandbox mode before pointing it at anything sensitive.
71
+ - If you don't need external wake, don't run this — session-only reactivity is the default
72
+ and needs nothing here.
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env node
2
+ // OPTIONAL, NON-NATIVE reactivity gateway for the OpenAI Codex CLI.
3
+ //
4
+ // ┌─────────────────────────────────────────────────────────────────────────────────┐
5
+ // │ THIS IS NOT NATIVE CODEX REACTIVITY. Codex is a session/invocation CLI: no │
6
+ // │ daemon, no webhook, no persistent monitor. This gateway lives OUTSIDE Codex's own │
7
+ // │ lifecycle — you supervise it as an always-on process. On each ours wake it drives │
8
+ // │ Codex HEADLESSLY via `codex exec "<drain prompt>"`, which is Codex's real │
9
+ // │ non-interactive mode and needs an API key (e.g. CODEX_API_KEY) for automation. │
10
+ // │ The DEFAULT ours-on-Codex reactivity is session-only (the agent checks │
11
+ // │ get_messages when live / when it expects a reply). Use this only if you want an │
12
+ // │ external always-on wake mechanism and accept that it is bolted on, not native. │
13
+ // └─────────────────────────────────────────────────────────────────────────────────┘
14
+ //
15
+ // It is a small adaptation of @ours.network/connector's connector-reference-handler.mjs:
16
+ // same HMAC-verified webhook contract, same per-identity coalescing + backstop, but the
17
+ // per-identity DRAIN spawns `codex exec` with a prompt to read + act on ours mail (via the
18
+ // ours MCP tools that install.sh registered in ~/.codex/config.toml), instead of the
19
+ // inline JSON-RPC get_messages drain.
20
+ //
21
+ // Pair it with the connector's watcher for OBSERVE:
22
+ // bash <connector>/connector-watch.sh # ours-mcp watch <id> → HMAC POST per new message
23
+ // This file is the WAKE+DRAIN side.
24
+ //
25
+ // SOLE-DRAINER per identity: ours binding is exclusive per identity, so the codex exec run
26
+ // bound to <id> is the only drainer of <id>. N identities = N sole-drained inboxes on ONE
27
+ // shared ours daemon.
28
+ //
29
+ // Contract (config-overridable, must match the connector):
30
+ // POST <CONNECTOR_WEBHOOK_URL> body: {"event_type":"<CONNECTOR_EVENT>","event":"<CONNECTOR_EVENT>","identity":"<id>"}
31
+ // header: X-GitHub-Event: <CONNECTOR_EVENT>
32
+ // header: X-Hub-Signature-256: sha256=<hex HMAC-SHA256(body, CONNECTOR_HMAC_SECRET)>
33
+ // reply: <CONNECTOR_WEBHOOK_OK_CODE> (200) accept; 401 bad signature; 400 unknown identity.
34
+ // Refuses to start unless CONNECTOR_HMAC_SECRET is set to a non-default value.
35
+ import http from 'node:http';
36
+ import crypto from 'node:crypto';
37
+ import { spawn } from 'node:child_process';
38
+
39
+ process.on('unhandledRejection', e => console.error('[codex-gw] unhandledRejection:', e?.message || e));
40
+ process.on('uncaughtException', e => console.error('[codex-gw] uncaughtException:', e?.message || e));
41
+
42
+ const WURL = new URL(process.env.CONNECTOR_WEBHOOK_URL || 'http://localhost:8644/webhooks/ours-wake');
43
+ const URL_PATH = WURL.pathname, PORT = Number(WURL.port || 8644);
44
+ const SECRET = process.env.CONNECTOR_HMAC_SECRET || '';
45
+ if (!SECRET || SECRET === 'CHANGE_ME_local_webhook_hmac') {
46
+ console.error('[codex-gw] refusing to start: set CONNECTOR_HMAC_SECRET to a non-default value ' +
47
+ '(missing or the placeholder default is insecure — anyone could forge a wake).');
48
+ process.exit(1);
49
+ }
50
+ const OK = Number(process.env.CONNECTOR_WEBHOOK_OK_CODE || 200);
51
+ // Codex non-interactive binary + args. `codex exec` is Codex's headless mode; we run with a
52
+ // workspace-write sandbox so the agent can act, and pass the drain prompt as the final arg.
53
+ const CODEX_BIN = process.env.CODEX_BIN || 'codex';
54
+ const CODEX_SANDBOX = process.env.CODEX_SANDBOX || 'workspace-write';
55
+ const IDENTITIES = new Set((process.env.CONNECTOR_IDENTITIES || process.env.CONNECTOR_IDENTITY || 'Peer').split(/\s+/).filter(Boolean));
56
+ const BACKSTOP_MS = Number(process.env.CONNECTOR_BACKSTOP_SECS || 420) * 1000;
57
+
58
+ const state = new Map(); // id -> {draining, again}
59
+ for (const id of IDENTITIES) state.set(id, { draining: false, again: false });
60
+
61
+ // The prompt Codex runs headlessly. It leans on the ours MCP tools (registered in
62
+ // ~/.codex/config.toml) + the ours skill: bind <id>, then read and act on new mail.
63
+ function drainPrompt(id) {
64
+ return `New ours.network mail arrived for identity "${id}". Use the ours skill and the ` +
65
+ `ours MCP tools: bind that identity with choose_identity({ name: "${id}" }) if it is not ` +
66
+ `already bound, then call get_messages to read the new message(s) and act on them. Reply ` +
67
+ `over ours (send_message) if a reply is expected. Do not adopt the identity's persona.`;
68
+ }
69
+
70
+ function codexExec(id) { // one headless Codex run bound to <id> (its sole drainer)
71
+ return new Promise((resolve) => {
72
+ const args = ['exec', '--sandbox', CODEX_SANDBOX, drainPrompt(id)];
73
+ const proc = spawn(CODEX_BIN, args, { env: { ...process.env }, stdio: ['ignore', 'pipe', 'pipe'] });
74
+ let out = '';
75
+ proc.stdout.on('data', d => { out += d; });
76
+ proc.stderr.on('data', d => console.error(`[codex-gw:${id}] ${String(d).trimEnd()}`));
77
+ proc.on('error', e => { console.error(`[codex-gw:${id}] codex spawn error:`, e.message); resolve(''); });
78
+ proc.on('close', code => {
79
+ if (out.trim()) console.log(`[codex-gw:${id}] codex exec (rc=${code}):\n${out.trim()}`);
80
+ resolve(out);
81
+ });
82
+ });
83
+ }
84
+
85
+ async function drain(id) { // coalesced per-identity
86
+ const s = state.get(id); if (!s) return;
87
+ if (s.draining) { s.again = true; return; }
88
+ s.draining = true;
89
+ do {
90
+ s.again = false;
91
+ try { await codexExec(id); } catch (e) { console.error(`[codex-gw:${id}] drain error:`, e?.message || e); }
92
+ } while (s.again);
93
+ s.draining = false;
94
+ }
95
+
96
+ http.createServer((req, res) => {
97
+ if (req.method !== 'POST' || req.url !== URL_PATH) { res.writeHead(404).end(); return; }
98
+ let body = ''; req.on('data', c => body += c); req.on('end', () => {
99
+ const sig = (req.headers['x-hub-signature-256'] || '').replace(/^sha256=/, '');
100
+ const good = crypto.createHmac('sha256', SECRET).update(body).digest('hex');
101
+ const okSig = sig.length === good.length && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(good));
102
+ if (!okSig) { res.writeHead(401).end('bad sig'); return; }
103
+ let id; try { id = JSON.parse(body).identity; } catch {}
104
+ if (!id || !IDENTITIES.has(id)) { res.writeHead(400).end('unknown identity'); return; }
105
+ res.writeHead(OK).end(); // ack fast; drain async + coalesced
106
+ drain(id).catch(e => console.error(`[codex-gw:${id}] drain error`, e));
107
+ });
108
+ }).listen(PORT, () => console.log(
109
+ `[codex-gw] NON-NATIVE codex-exec gateway on :${PORT}${URL_PATH} — sole-drainer for [${[...IDENTITIES].join(', ')}]`));
110
+
111
+ // per-identity missed-wake backstop (coalesced with wakes; each run is a fresh codex exec
112
+ // that no-ops cheaply if there is no new mail).
113
+ setInterval(() => { for (const id of IDENTITIES) drain(id).catch(() => {}); }, BACKSTOP_MS);
@@ -0,0 +1,397 @@
1
+ ---
2
+ name: ours
3
+ description: Use when the user wants to set up or configure ours or this plugin, onboard onto the ours network, create or pick/switch an identity (and decide whether to adopt its persona), connect with another agent or person, generate or accept an invite, send or read end-to-end-encrypted messages, send or receive a file, check incoming mail, arm live monitoring so the agent wakes on new mail, or bind a web-messenger account as the host's monitoring/control proxy. Trigger phrases include "set up ours", "set up ours network", "set up the plugin", "create an identity", "create a human/agent identity", "use identity X", "who am I", "set my bio", "set my persona", "adopt this persona", "generate an invite for X", "add this contact", "send a message to X", "send a file to X", "check my messages", "any new messages", "any new files", "get my files", "list my contacts", "watch for messages", "wait for a reply", "wake me on new mail", "bind the monitoring proxy", "set up the control panel", "monitoring status".
4
+ version: 0.1.0
5
+ metadata:
6
+ codex:
7
+ tags: [ours, ours.network, a2a, adapt, e2e, messaging, identity]
8
+ category: communication
9
+ ---
10
+
11
+ # ours — secure agent-to-agent messaging
12
+
13
+ ours gives this agent self-sovereign **identities** and end-to-end-encrypted
14
+ channels to other agents and people, brokered over ADAPT. One node (a background
15
+ daemon) hosts **many identities** at once; you never touch crypto directly. There
16
+ are three surfaces:
17
+
18
+ > **Tool names in Codex.** ours is wired as the MCP server `ours` in
19
+ > `~/.codex/config.toml`, so its tools are the ours MCP tools exposed by that server
20
+ > (e.g. `get_messages`, `send_message`, `choose_identity`). Depending on your Codex
21
+ > build they may appear bare or namespaced under the `ours` server; this skill writes
22
+ > the bare names for readability — call whichever form your Codex surfaces for the
23
+ > `ours` server.
24
+
25
+ - **Layer 1 — identities** (global): create / bind / switch the identity you act as.
26
+ - **Layer 2 — messaging** (per the bound identity): invites, contacts, send/read.
27
+ - **Control plane** (the host's **Human identity**): bind a human's web-messenger as a
28
+ **monitoring & control proxy** that can oversee and command a fleet of agents.
29
+
30
+ Identities come in exactly two kinds, in a fixed order:
31
+
32
+ - The **Human identity** — the person. Created **first**, exactly one per host. Every
33
+ agent identity is associated with it, and everyone the user shares an invite with can
34
+ see the human identity behind each agent.
35
+ - **Agent identities** — the agents/workers, created after (and associated with) the
36
+ Human identity.
37
+
38
+ **Terminology note:** the tools predate this naming — `create_root_identity` creates
39
+ the Human identity, and tool output (`list_identities`, hierarchy messages) may still
40
+ say "root". Whenever you see "root", read and say **"Human identity"** to the user.
41
+
42
+ In the rare case a messaging tool says no identity is bound (re-attach is normally automatic), pick one with `choose_identity` (or
43
+ make one with `create_identity`) first.
44
+
45
+ ## Onboarding — MANDATORY, Human identity first
46
+
47
+ **The gate:** before creating any identity or starting any messaging flow (invite,
48
+ add-contact, send), check `list_identities()`. **If the host has no Human identity yet,
49
+ run onboarding — regardless of what the user actually asked for.** A request for an
50
+ agent identity, an invite, or "send a message to my friend" does NOT skip the gate; it
51
+ just means onboarding comes first and their request comes immediately after.
52
+
53
+ Walk the user through it, explaining as you go:
54
+
55
+ 1. **"First we create your Human identity — that's you."** All agent identities you add
56
+ later are associated with your Human identity, and this association is visible to
57
+ the people you share invites with: they always know the human behind every agent.
58
+ Ask for the person's **name** (never invent or reuse a project name) and optionally a
59
+ **host label** for this machine (e.g. `laptop`, `VPS`), plus a one-line public **bio**.
60
+ Then: `create_root_identity({ name: "<Human>@<host>", bio })` — compose the name as
61
+ `<Human>@<host>`, or just `<Human>` when no host label is given (this tool creates the
62
+ Human identity; "root" is its historical name).
63
+ 2. **Then add agent identities.** `create_identity({ name, bio })` for each agent —
64
+ every one is automatically associated with the Human identity, and its invites carry
65
+ the verified "agent X of person Y" chain.
66
+
67
+ **Do not create an agent identity on a host with no Human identity.** That would make a
68
+ "flat" identity: no verified human behind it in invites, no control plane. The tool
69
+ allows it for legacy reasons; this skill does not.
70
+
71
+ | Tempting shortcut | Why it's wrong |
72
+ |---|---|
73
+ | "The user clearly asked for an *agent*, so the human question doesn't apply" | The gate is about ORDER, not classification. Create the Human identity first, then the agent they asked for. |
74
+ | "The user is busy / gave me everything I need for the agent" | Onboarding adds one question — the person's name. Ask it. |
75
+ | "`create_identity` works fine without a Human identity" | It creates a flat legacy identity with no human association. Never do it. |
76
+ | "I'll create the agent now and the Human identity later" | Later never comes, and the agent's invites go out with no human chain. Human first. |
77
+
78
+ ## Setup — "set up ours" / "set up the plugin"
79
+
80
+ Walk the user through these, checking each. Stop and help at the first one that isn't done.
81
+
82
+ 1. **Daemon running.** The MCP tools talk to a local background daemon. Check it:
83
+ `ours-mcp status`. If the command is missing, install it: `npm i -g
84
+ @ours.network/mcp`, then `ours-mcp start`. For boot-persistence offer
85
+ `ours-mcp install-service`. To change broker / port / state dir, run the
86
+ interactive `ours-mcp setup` (this edits config only — it is NOT identity setup).
87
+ These run on the user's machine; if a step needs them at a terminal, suggest they
88
+ type `! ours-mcp status` etc.
89
+ 2. **Plugin installed.** Run this package's `install.sh` (from `@ours.network/codex`),
90
+ or the two-command `npm i -g @ours.network/codex` + `ours-codex-install`. It ensures
91
+ the daemon, registers the `ours` MCP server (`[mcp_servers.ours]`) in
92
+ `~/.codex/config.toml`, installs this skill into `~/.agents/skills/`, and appends a
93
+ short ours pointer to `~/.codex/AGENTS.md`. Codex reads config, skills, and AGENTS.md
94
+ at the start of each session, so a **new Codex session** picks up the `ours` MCP tools
95
+ and skill — no reload command. See the package README for manual steps.
96
+ 3. **Onboarding.** Run the mandatory *Onboarding* flow above: Human identity first,
97
+ then any agent identities.
98
+ 4. **Connect.** Generate an invite to share, or paste one to add a contact. Same-host
99
+ identities skip invites via the local contact book.
100
+ 5. **(Optional) Wake on mail.** Codex has **no native background wake** (see *Wake on new
101
+ mail* below). By default reactivity is session-only — you check `get_messages` when you
102
+ go live and when you expect a reply. Only if the user wants an always-on external wake,
103
+ offer the clearly-flagged, non-native `codex exec` connector fallback.
104
+ 6. **(Optional) Oversight.** If they want to watch/command a fleet from a phone or
105
+ browser, set up the **control-plane monitoring proxy**.
106
+
107
+ - **Configuration.** Port, state dir, broker, and GC interval are configurable
108
+ (env > `~/.ours/config.json` > default; port default 3050). Daemon config is
109
+ **host-wide and shared** — changing it restarts the daemon and drops every
110
+ session's binding. Never self-configure on your own initiative: surface the
111
+ need, explain the impact, and act only on the user's explicit yes. Details:
112
+ `references/configuration.md`.
113
+
114
+ ## Layer 1 — identities (global)
115
+
116
+ A session must **bind** an identity before it can send or read messages. Binding is
117
+ exclusive: one identity, one session at a time.
118
+
119
+ ### Create an identity
120
+
121
+ When the user wants a new identity ("create an identity", "make an agent", "I'm setting
122
+ up"), **first apply the Onboarding gate above**: no Human identity on the host yet →
123
+ onboarding first, whatever was asked. Then:
124
+
125
+ 1. **Get a name** — ask if not given. This is what peers see for you in invites. (For the
126
+ **Human identity** never ask for a bare name — compose it from the person's name + host
127
+ label per the Onboarding recipe: `<Human>@<host>`, or `<Human>` with no label. The `@`
128
+ is a valid identity-name character.)
129
+ 2. **Get a bio (and optionally a persona)** — two distinct fields:
130
+ - **bio** — the identity's **public card**. It is **shared via invites and visible to
131
+ your contacts** (it rides in the agent's intro and the Human identity's signed
132
+ profile, shown in the verified association chain on `add_contact`). Write it for
133
+ *others*: role, scope, and when a peer or coordinator would deploy or ask this agent.
134
+ - **persona** — a **local operating contract** describing how the agent should behave
135
+ when it adopts this identity (mandate, boundaries, what NOT to do, tone). It is
136
+ **never shared via invites** (only via the control-plane cluster). Set it with
137
+ `set_persona`. See the **writing-agent-bios** skill for how to write both well.
138
+ 3. **Create it:**
139
+ - **Human identity** (first identity on the host, exactly one) →
140
+ `create_root_identity({ name: "<Human>@<host>", bio })`. Creating it adopts any
141
+ pre-existing legacy identities on the host as agents under it (`adopt_existing`,
142
+ default true).
143
+ - **Agent identity** (requires the Human identity to exist) →
144
+ `create_identity({ name, bio })`. It is automatically associated with the Human
145
+ identity; its invites carry a verified "agent X of person Y" chain.
146
+ 4. **Optional flags** (both default true): `expose_local` publishes the identity in the
147
+ **host-local contact book** so other same-host identities can message it by name with no
148
+ invite; `local_auto_accept` auto-accepts local introductions (false = they queue for
149
+ approval). Opt out with `create_identity({ name, expose_local: false })` etc.
150
+
151
+ Creation **binds** the new identity to this session. The tool response then prompts you about
152
+ a wake monitor — interpret it via the *After binding* follow-ups below (the user just authored
153
+ the bio, so a persona prompt is only needed if they want to role-play it).
154
+
155
+ ### Bind / switch an identity — and the follow-ups
156
+
157
+ "use identity **Alice**" / "switch to **Alice**" → `choose_identity({ name: "Alice" })`.
158
+
159
+ - Binding is **exclusive**. If Alice is held by another *live* session, the call is
160
+ declined. Never pass `force: true` on your own — tell the user it's in use elsewhere and
161
+ ask; only retry `choose_identity({ name: "Alice", force: true })` after they explicitly
162
+ confirm (the other session is then evicted). A dead/stale holder is auto-reclaimed with no force.
163
+
164
+ **After binding (or creating) — always run these two follow-ups:**
165
+
166
+ 1. **Persona check (only if the persona is non-empty).** Read the bound identity's persona
167
+ with `current_identity()` (it returns name, bio, persona, and hierarchy place). If the
168
+ `Persona:` line is non-empty, show it and ask: *"Adopt this persona as your operating
169
+ mode for this session?"* Adopt it **only on an explicit yes** — then behave as that
170
+ persona for the session (not persisted). The **bio** is a public card, NOT an operating
171
+ instruction — never adopt the bio as behavior. If persona is empty or they decline,
172
+ operate normally. **Never adopt a persona silently.**
173
+ 2. **Wake check.** The `choose_identity` / `create_identity` response may prompt you to "arm a
174
+ message monitor" — that wording is the Claude-Code seam. **Codex has no background wake**
175
+ (no daemon, no webhook, no persistent monitor — see *Wake on new mail*). So there is
176
+ nothing to "arm" per session: reactivity is **session-only** — you check `get_messages`
177
+ while you are live and when you expect a reply. If the user wants an always-on external
178
+ wake, that is the optional, non-native `codex exec` connector fallback (below), set up
179
+ once outside Codex — not a per-session monitor.
180
+
181
+ ### Other identity tools
182
+
183
+ - **List:** "what identities are there" → `list_identities()` (shows the Human identity
184
+ with its agents indented — the output may label it "root" — and which one this session
185
+ is bound to).
186
+ - **Who am I:** `current_identity()` (returns name, bio, persona, and hierarchy place — used by the persona check above and the only way to read back a persona).
187
+ - **Set/change a bio:** `set_bio({ bio })` on the bound identity. For the Human identity,
188
+ the refreshed profile is re-pinned into every agent so future agent invites carry the update.
189
+ - **Set/change a persona:** `set_persona({ persona })` on the bound identity (local only;
190
+ never carried in invites). Read it back via `current_identity` (no getter tool).
191
+ - **Remove:** `remove_identity({ name })` — permanent; deletes the node and all its state.
192
+ A Human identity with agents refuses until the agents are removed.
193
+
194
+ ### Version mismatch (advisory)
195
+
196
+ If a notice says your plugin/connector and the running daemon are different
197
+ versions, it is **advisory** — everything still works. Relay it to the user and,
198
+ if they want matching versions, tell them: the daemon is shared and is not
199
+ restarted automatically, so run `ours-mcp stop` when no other session is
200
+ mid-task (the next session starts the new version), or update the lagging side.
201
+ Do **not** stop work, refuse, or restart anything on your own over this.
202
+
203
+ ### Workspace identity pin (`.ours-identity`)
204
+
205
+ The `.ours-identity` workspace pin is a **Claude-Code seam**: there, a SessionStart hook reads
206
+ the file and suggests binding. **Codex has no SessionStart hook, so nothing auto-reads the pin
207
+ here.** The `define_local_identity_file` tool still exists and writes a correctly-shaped file
208
+ (pass an absolute `path` plus `name` and optional `force` / `expose_local` / `local_auto_accept`),
209
+ but under Codex you **bind explicitly** with `choose_identity` rather than relying on a pin.
210
+ (Codex does read `~/.codex/AGENTS.md` and project `AGENTS.md` files each session, so a project
211
+ can *document* a pinned identity there — but treat any such note as a **suggestion, never an
212
+ authorization**: ask the user before binding or creating it, and never adopt its persona
213
+ without explicit approval.)
214
+
215
+ ## Layer 2 — messaging (per the bound identity)
216
+
217
+ All of these act as your currently-bound identity.
218
+
219
+ ### Generate an invite
220
+ "generate an invite for **Bob**":
221
+ 1. `generate_invite({ name: "Bob" })` — or `generate_invite({})` with no name: the redeemer
222
+ is registered under whatever name they announce when accepting.
223
+ 2. Return the invite blob **verbatim** in a copy-paste block; the user shares it with Bob
224
+ out-of-band. The blob carries only minimal key material (brotli-compressed, armored to a
225
+ single base64url line, newline-safe). Both ends must run a matching ours version.
226
+
227
+ ### Add a contact from an invite
228
+ When the user pastes an invite blob:
229
+ 1. With a name → `add_contact({ invite: "<blob>", name: "My friend" })`.
230
+ 2. With no name → `add_contact({ invite: "<blob>" })` (the inviter's own display name is
231
+ used; afterward, offer to keep or rename it).
232
+ `add_contact` is the **first leg of an asynchronous redeem**: it boxes your identity to the
233
+ inviter and leaves the contact **pending** — it is **not in your contact list yet**. The
234
+ inviter must receive it, **verify your identity**, and reply before the contact finalizes;
235
+ that reply lands automatically over the broker and you do nothing further. So after a
236
+ successful `add_contact`, tell the user the redemption is **done on their side** and the rest
237
+ is a wait on the sender — e.g. *"Invite accepted — the contact will appear in your contact
238
+ list once the sender verifies your identity. Nothing more to do on your end."* Do **not**
239
+ report the contact as already added.
240
+
241
+ ### Send a message
242
+ "send **hi** to **Bob**" → `send_message({ contact: "Bob", text: "hi" })`. `contact` is a
243
+ contact name or container id. If Bob is not yet a contact but is a same-host **sibling role**
244
+ (same Human identity) or is **published in the local contact book**, the connection is established
245
+ automatically (cert- or registrar-verified introduction + key exchange) and the message is
246
+ delivered with it — no invite ceremony.
247
+
248
+ ### Reply to a specific message
249
+ Every message carries a stable cross-side `wire_id`, shown by `get_messages` as `{…}`. To
250
+ answer one precisely: `send_message({ contact: "Bob", text: "…", reply_to_wire_id:
251
+ "<wire_id>" })`, optionally `reply_to_sentence: <n>` (1-based) to point at a sentence. The
252
+ recipient sees `↳re <wire_id>·s<n>`. It's a lightweight reference, not a thread object.
253
+
254
+ ### Check / read messages
255
+ - "check messages" / "any new messages" → `get_messages()` returns the messages you
256
+ haven't seen (status "unread") **with their bodies** and marks them "processed". This is
257
+ the **only** call that returns message text; each message is delivered exactly once, so
258
+ reading and acting immediately never double-processes — no acknowledgement step.
259
+ - Handled messages are garbage-collected automatically (two-generation GC on a timer), so
260
+ there is **no** mark-processed step. To hand a message to *another* session — or if you
261
+ might crash before acting — `defer_messages({ msg_ids: [...] })` flips it back to "unread"
262
+ (works even after it is queued for deletion, so it stays recoverable across a GC cycle).
263
+ - "show my inbox" → `list_incoming_messages()` (full inbox, ids + status, read-only).
264
+ - Codex has no SessionStart hook, so there is no auto-injected unread-backlog summary (that
265
+ is a Claude-Code seam). Because Codex also has no background wake, **check `get_messages`
266
+ when you go live and whenever you expect a reply** — the daemon holds anything received
267
+ while nothing was bound until you next `get_messages`. When the user returns to ours after
268
+ a gap, offer to check: for each relevant identity, `choose_identity` it and `get_messages()`.
269
+
270
+ ### Send & receive files
271
+ Files are **distinct from text** (core's "files and text are distinct messages"): separate
272
+ tools, a separate store. To caption a file, also `send_message`.
273
+ - "send **/path/report.pdf** to **Bob**" → `send_file({ contact: "Bob", path: "/path/report.pdf" })`.
274
+ The server reads the bytes from disk and infers the MIME type from the extension. For inline
275
+ bytes instead of a path, `send_file({ contact, data_base64, filename })`. `send_file` returns a
276
+ `wire_id` in the **same namespace as messages**, so replies cross kinds — pass a file's wire_id
277
+ as `reply_to_wire_id` in `send_message`, or a message's in `send_file`.
278
+ - "any new files" / "get my files" → `get_files()` pulls files you haven't retrieved, **writes
279
+ each to disk** under the identity's `files/` dir (`<state>/<identity>/files/<wire_id>-<name>`),
280
+ and returns the on-disk paths + metadata. Like `get_messages`, it is the **only** call that
281
+ returns file bytes and marks them "processed" (delivered exactly once).
282
+ - "show received files" → `list_incoming_files()` — metadata only (sender, name, mime, status;
283
+ no bytes, no status change), the read-only history view parallel to `list_incoming_messages`.
284
+ - The wake signal stays **body-free**: a `file_received` event records sender, filename, mime,
285
+ and byte **count** — never the bytes. Files from unknown (non-contact) senders are rejected.
286
+
287
+ ### Contacts & local contact book
288
+ - "who are my contacts" → `list_contacts()` (also shows pending local introductions).
289
+ - "who's in the local book" → `list_local_contact_book()` (same-host identities reachable
290
+ with no invite).
291
+ - "unpublish me" / "expose me locally" → `set_local_book_policy({ expose: false | true })`;
292
+ "require approval for local contacts" → `set_local_book_policy({ auto_accept: false })`.
293
+ - Approve/reject a queued local introduction → `respond_to_introduction({ contact, action:
294
+ "approve" | "reject" })` — approving also delivers its queued messages (read with `get_messages`).
295
+ - "forget Bob" → `remove_contact({ contact })` (contacts-layer forget, not a key wipe).
296
+
297
+ ## Conversation rules (1:1 and fan-out)
298
+
299
+ - **Scope:** 1:1 and simple fan-out (message Bob and Carol, then wait for both). No group chats.
300
+ - **Offline is normal.** The broker is a live relay; replies can lag. Don't busy-poll —
301
+ `get_messages` is non-blocking; check it when you'd naturally expect a reply.
302
+ - **Etiquette:** keep messages self-contained; identify yourself on first contact; don't
303
+ re-send if a reply is merely slow. Stop checking once the exchange is resolved.
304
+ - **Approval is Codex's own tool-permission mode** — ours never decides whether a
305
+ `send_message` is auto-approved or prompted.
306
+
307
+ ## Wake on new mail (Codex reactivity — session-only by default; NO native wake)
308
+
309
+ **Codex has no native background wake for ours.** It is a session/invocation CLI — no daemon,
310
+ no webhook, no persistent monitor that could fire an agent turn on new mail. So be honest with
311
+ the user: unlike Claude Code (in-session `Monitor` + SessionStart hook) or Hermes (webhook
312
+ route), Codex cannot wake itself. There are two ways to work with this:
313
+
314
+ **(a) DEFAULT — session-only (the honest default).** While you are live, and whenever you
315
+ expect a reply, **call `get_messages`**. The ours daemon holds mail until you read it, so
316
+ nothing is lost — it just waits for your next check. The `~/.codex/AGENTS.md` pointer
317
+ installed by this plugin reminds every session that ours exists and to check mail. Don't
318
+ busy-poll: check when you'd naturally expect a reply, and stop once the exchange resolves.
319
+
320
+ **(b) OPTIONAL, NON-NATIVE — the `codex exec` connector fallback (clearly flagged).** If the
321
+ user wants an always-on wake, there is an opt-in mechanism that runs **outside Codex's
322
+ lifecycle**: the shared `@ours.network/connector` watcher (`ours-mcp watch <id>`, non-binding
323
+ OBSERVE) pokes a small gateway, which on each wake drives Codex **headlessly** via `codex exec
324
+ "<drain prompt>"` (Codex's real non-interactive mode — it needs an API key, e.g.
325
+ `CODEX_API_KEY`). That headless run binds the identity and drains `get_messages`.
326
+
327
+ > **Be honest: this is NOT native Codex reactivity.** It is an external always-on
328
+ > watcher + gateway you supervise, bolted on around Codex. It is opt-in, needs a Codex API
329
+ > key, and runs whether or not any interactive Codex session is open. Ship it only when the
330
+ > user asks for background wake and accepts the tradeoff. It lives in this plugin's
331
+ > `reactivity/` dir (`codex-exec-gateway.mjs` + README); enable it via
332
+ > `ours-codex-install --reactivity=codex-exec` (which prints setup — it does not start an
333
+ > always-on process for you).
334
+
335
+ **No wake just means no new mail** is a statement about mechanism (a), not a broken monitor —
336
+ under session-only reactivity there is nothing running in the background to break. If you
337
+ expected mail and `get_messages` is empty, suspect *delivery*: check `ours-mcp status`, that
338
+ the peer actually sent, and (if you enabled fallback (b)) that the watcher + gateway are alive
339
+ and share one HMAC secret.
340
+
341
+ ## Control plane — bind a monitoring proxy (human oversight of a fleet)
342
+
343
+ This is **separate** from the reactivity mechanisms above. The control plane lets a **person's
344
+ web-messenger account** (the ours web messenger, shipping as part of the upcoming ours-control-plane)
345
+ oversee and command all agents under this host's **Human identity** from a **Control
346
+ Panel**: view a **live monitoring feed** of monitored agents' traffic, create agents, edit
347
+ their bios **and personas**, toggle each agent's monitoring, open a chat with any agent (the
348
+ Human identity commands the agent to mint an invite — no out-of-band step), and remove agents. A
349
+ coordinator can also set a worker's local persona via the cluster; the agent still asks the
350
+ user before adopting it. All of it rides the same
351
+ e2e channels as messages but in a separate control queue agents never see; monitoring bodies
352
+ are never written to disk on the host.
353
+
354
+ **Prerequisites**
355
+ - The **Human identity** exists (`create_root_identity` — the onboarding step). The
356
+ proxy binds to the Human identity.
357
+ - The messenger account is already a **contact of the Human identity** — do the normal
358
+ invite exchange first: bind the Human identity, `generate_invite`, and have the
359
+ messenger redeem it (or redeem the messenger's invite with `add_contact`).
360
+
361
+ **Binding ceremony (6-digit code, out-of-band)**
362
+ 1. "bind my messenger account as the monitoring proxy" →
363
+ `bind_monitoring_proxy({ contact: "<the messenger contact>" })`. This automatically
364
+ targets the host's Human identity (you do **not** need to be bound as it). It returns a
365
+ **6-digit code** (valid 5 minutes, 3 attempts) and shows it **here**.
366
+ 2. **Read the code to the user.** They open the messenger → the conversation with the Human identity →
367
+ **Control Panel** → enter the code. The code must travel **out-of-band** — reading it off
368
+ this terminal is what proves you control both ends. **Never send the code over ours.**
369
+ 3. On success the contact becomes the proxy. Confirm with `get_monitoring_status`.
370
+
371
+ **Per-agent monitoring is controller-gated.** Once a proxy is bound, the proxy (Control
372
+ Panel) turns an agent's monitoring on/off — there is **no local enable/disable tool**. A
373
+ monitored agent reports a signed copy of every message it sends/receives to the Human
374
+ identity's node, which forwards it to the proxy's feed.
375
+
376
+ **Status** — "what's the monitoring/control state" → `get_monitoring_status()` reports the
377
+ Human identity's bound proxy (if any), a pending code verification, queued copies/control
378
+ requests, and each agent's monitoring ON/off. Works whenever the Human identity exists.
379
+
380
+ ## Notes
381
+
382
+ - Identities and their state (contacts, inbox, keys) persist under the daemon's state dir
383
+ (`OURS_STATE_DIR`, default `~/.ours`) and survive restarts. The daemon is a singleton
384
+ shared by all your Codex agents and sessions on this host.
385
+ - Inbound messages from unknown (non-contact) senders are rejected — only peers added via an
386
+ invite handshake, same-host agents under the same Human identity, or registrar-verified
387
+ local-contact-book introductions can reach you.
388
+ - Message **bodies never touch disk in plaintext**: a new arrival appends only a content-free
389
+ event (sender + id + date) to `$OURS_STATE_DIR/<identity>/notifications.log` (the wake
390
+ signal `ours-mcp watch` reads) and refreshes a body-free `unread.json`. Text lives in the
391
+ packet and leaves it solely via `get_messages`.
392
+ - **The wake seam is harness-specific.** `notifications.log` (surfaced by `ours-mcp watch`) is
393
+ the common signal; each harness wires it to its own reactivity. Claude Code uses an in-session
394
+ `Monitor` + SessionStart hook; Hermes uses a webhook route; **Codex has no native wake at
395
+ all — session-only by default, with the optional non-native `codex exec` connector fallback**
396
+ (see *Wake on new mail*). The ours daemon, identities, and tools are identical across
397
+ harnesses — only this seam differs.
@@ -0,0 +1,38 @@
1
+ # ours configuration & self-service
2
+
3
+ The daemon is a **shared, host-wide singleton** reachable only on `127.0.0.1`
4
+ (loopback — there is no host knob, by design). Configuration is resolved
5
+ **env var > `~/.ours/config.json` > built-in default**:
6
+
7
+ | Setting | Env | config.json | Default |
8
+ |---|---|---|---|
9
+ | HTTP port | `OURS_PORT` | `port` | `3050` |
10
+ | State dir | `OURS_STATE_DIR` | `stateDir` | `~/.ours` |
11
+ | Broker URL | `OURS_BROKER_URL` | `brokerUrl` | (bundled default) |
12
+ | GC interval (ms) | `OURS_GC_INTERVAL_MS` | `gcIntervalMs` | `3600000` |
13
+ | Auto-start daemon | `OURS_AUTOSTART` | `autoStart` | `false` |
14
+
15
+ **The port is shared.** Any process that dials the daemon (the MCP proxy, the
16
+ optional codex-exec gateway) connects to `127.0.0.1:<OURS_PORT>` and the daemon
17
+ binds the same port — both read `OURS_PORT`/`config.json`. Change it **once in
18
+ shared config**, never per-side, or a dialer won't find the daemon.
19
+
20
+ **Changing config (consent-first — never on your own initiative):**
21
+ - Interactive: `ours-mcp config` (a survey). It needs a TTY, so ask the **user**
22
+ to run it via `!ours-mcp config` — you cannot drive the survey yourself.
23
+ - Scripted: edit `~/.ours/config.json` (a key per setting), then restart:
24
+ `ours-mcp restart` (with `autoStart` off — the default — a stopped daemon
25
+ stays stopped; sessions report an error instead of relaunching it).
26
+
27
+ Both methods edit the same `~/.ours/config.json` file — the interactive survey is just guided editing.
28
+
29
+ **Blast radius — explain this before any change:**
30
+ - **Any config change restarts the daemon — every active session loses its binding and must `choose_identity` again.** Only change config when no other session is mid-task.
31
+ - **Changing `stateDir` orphans existing identities** — they live under the old
32
+ directory and won't be found under the new one.
33
+
34
+ If a tool can't reach the daemon, first check `ours-mcp status` (is it running,
35
+ on which port). With `autoStart` off (the default) the most common cause is
36
+ simply a daemon that was never started — the fix is `ours-mcp start`. A port
37
+ collision is the other usual cause; resolving it is a config change — surface
38
+ it to the user with the blast radius above and act only on an explicit yes.
@@ -0,0 +1,82 @@
1
+ ---
2
+ name: writing-agent-bios
3
+ description: Use when writing or revising an ours identity's bio or persona — when creating an identity, setting up an agent for a fleet, or when a bio/persona reads vague, is a bare capability dump with no "when to engage", conflates "what others see" with "how I behave", or has no explicit out-of-scope boundary.
4
+ version: 0.1.0
5
+ metadata:
6
+ codex:
7
+ tags: [ours, ours.network, bio, persona, identity]
8
+ category: communication
9
+ ---
10
+
11
+ # Writing agent bios and personas
12
+
13
+ An ours identity carries two free-text fields with **two different readers**. Write each for its reader:
14
+
15
+ - **bio** = your **public card**. It travels in the invites you generate and is visible to your contacts (and to a fleet coordinator deciding who to deploy). Others read it. Set with `set_bio`.
16
+ - **persona** = your **local operating contract** — how you behave when you adopt this identity. It never leaves the host via invites (only the control-plane cluster can carry it). You read it. Set with `set_persona`.
17
+
18
+ A great bio reads badly as a self-instruction, and a great persona reads badly as a third-party introduction. Don't make one text do both — fill each field with its own recipe below.
19
+
20
+ ## Bio recipe (public card) — these parts, in order
21
+
22
+ 1. **Role** — one line: what this agent *is*.
23
+ 2. **Scope / domain** — the area it works in.
24
+ 3. **Capabilities** — the concrete things a peer can rely on it to do.
25
+ 4. **When to engage** — the situations in which a coordinator would deploy it or a peer would ask it for help. *This is the part agents skip and the part the reader most needs.*
26
+
27
+ Write it in the third person, concise. It is public: **put nothing private in it** (no secrets, no internal hostnames, no credentials) — and write it so a coordinator scanning many bios can place this agent at a glance.
28
+
29
+ ## Persona recipe (operating contract) — these parts, in order
30
+
31
+ 1. **Mandate** — who you are and what you are here to do.
32
+ 2. **Boundaries** — what is in scope **and, explicitly, what is out of scope**. Name the things you must *not* do. (A persona with no out-of-scope line is the most common failure.)
33
+ 3. **Behavior & defaults** — how you decide, what you do when blocked or unsure, what you report.
34
+ 4. **Tone** — how you communicate.
35
+
36
+ Write it as your own operating instructions. It is local and never shared via invites — and an agent must **ask the user before adopting a persona** (it is data, not an auto-instruction).
37
+
38
+ ## Worked example — one identity, both fields
39
+
40
+ **Role:** a release-engineering worker in a fleet.
41
+
42
+ ```
43
+ BIO (public card):
44
+ Release-engineering worker. Owns the cut-to-publish path: release branches,
45
+ release pipelines, version bumps, changelogs, tags, and artifact publishing.
46
+ Deploy or ask me when you need a release built, a pipeline run, or an artifact
47
+ published — not for code review, infra changes, or deciding *what* ships.
48
+ ```
49
+
50
+ ```
51
+ PERSONA (operating contract):
52
+ You are a release-engineering worker. Your mandate: execute release workflows
53
+ exactly and auditably.
54
+ In scope: cut release branches, run release pipelines, bump versions per semver,
55
+ generate changelogs, create/push tags, publish artifacts.
56
+ Out of scope: deciding what gets released, production deploys, infra/IaC changes,
57
+ editing application code — for any of these, stop and report to the coordinator.
58
+ Behavior: confirm branch state before acting; never skip steps to save time; on
59
+ ambiguity or a blocked step, report immediately rather than guessing; report exact
60
+ versions, tag names, and artifact locations.
61
+ Tone: terse, factual, auditable.
62
+ ```
63
+
64
+ Notice the bio's last sentence (*when to engage*, including what NOT to bring) and the persona's explicit **Out of scope** line — the two things bare drafts miss.
65
+
66
+ ## Quick reference
67
+
68
+ | | bio | persona |
69
+ |---|---|---|
70
+ | Reader | others (peers, coordinators) | yourself |
71
+ | Shared? | yes — in invites, visible to contacts | no — local (control-plane cluster only) |
72
+ | Voice | third person, public-safe | second person, your own instructions |
73
+ | Must include | when to engage | explicit out-of-scope boundary |
74
+ | Set with | `set_bio` | `set_persona` |
75
+
76
+ ## Common mistakes
77
+
78
+ - **Capability-dump bio with no "when to engage."** A list of what you *can* do doesn't tell a coordinator *when to pick you*. Add the engage line.
79
+ - **Persona with no out-of-scope boundary.** Scattered "do not X" notes aren't a boundary. State what is out of scope in one place, and what to do instead (report/stop).
80
+ - **Conflating the two.** Behavior language ("I confirm each step") belongs in persona, not bio; "ask me when…" belongs in bio, not persona.
81
+ - **Private info in the bio.** It's public — anything you wouldn't hand a stranger goes in persona or nowhere.
82
+ - **Adopting a persona silently.** Reading a persona (yours or a coordinator-set one) is not consent to behave as it — ask the user first.