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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ npx github:treylom/ThisCodex doctor --non-interactive # (터미널) 설치 검
|---|---|---|
| Codex CLI를 상시 디스코드 봇으로 | ✅ 작동 | `codex app-server`(화면 없는 백그라운드 실행) + 파이썬 bridge(다리) 데몬 `bot.py` + discord.py |
| 멀티 클라이언트 동일 스레드(봇 대화를 터미널 화면으로 관전·개입) | ✅ 작동 | 같은 app-server에 `codex resume <스레드ID> --remote ws://…` |
| 페르소나·볼트 규칙 자동 로드 | ✅ 작동 | `~/.codex/config.toml`의 `project_doc_fallback_filenames = ["SOUL.md","AGENTS.md"]` |
| 페르소나·볼트 규칙 자동 로드 | ✅ 작동 | 정본 `BOT_WD/AGENTS.md`; `SOUL.md`는 `AGENTS.md`가 없을 때만 쓰는 구형 fallback |
| 봇끼리 호출 + 회의 규율 | ✅ 작동 | `bot-roster.yaml`(단일 기준 파일)을 세션 시작 시 주입 |
| 안전 기본 / YOLO(전체 권한) opt-in | ✅ 작동 | 기본 `workspace-write`, `THISCODEX_YOLO=1` 시에만 `thread/start`·`thread/resume` 둘 다 `danger-full-access`·`never` 전송 ([계약](docs/yolo-bridge-contract.md)) |
| 이미지 생성 | ✅ 작동 | codex 내장 `image_gen.imagegen` 도구 |
Expand Down Expand Up @@ -205,7 +205,8 @@ zsh가 없어도 실행되게 합니다.

### 3.2 `~/.codex/config.toml`
```toml
project_doc_fallback_filenames = ["SOUL.md", "AGENTS.md"]
# AGENTS.md가 Codex 정본 파일명이며 SOUL.md는 구형 fallback 전용.
project_doc_fallback_filenames = ["SOUL.md"]
project_doc_max_bytes = 65536

[mcp_servers.discord]
Expand All @@ -216,7 +217,12 @@ DISCORD_STATE_DIR = "~/.claude/channels/discord-<봇이름>"
```

### 3.3 봇 작업 디렉토리
`SOUL.md`(페르소나)와 `AGENTS.md`(규칙 — 정적 디스코드 답장 규칙 포함, §4)를 봇 작업 폴더에 둠. 매 스레드 자동 로드되므로 **매 턴 페르소나 텍스트 재주입 금지**.
봇 작업 폴더에는 정본 `AGENTS.md` 하나를 둔다. 이 파일 안에 SOUL v2
페르소나 capsule과 정적 디스코드 답장 규칙(§4)을 넣고 rules 라우터를
가리킨다. Codex는 디렉터리마다 지시 파일 하나만 선택하므로 같은 폴더의
`SOUL.md`를 `AGENTS.md`와 함께 로드하지 않는다. `SOUL.md`는 `AGENTS.md`가
없는 구형 설치의 fallback으로만 남긴다. **매 턴 정적 페르소나 텍스트를
재주입하지 않는다.**

### 3.4 실행 (접근 권한을 주는 건 bridge)
2-윈도우 tmux 런처(`scripts/launch.sh`): `infra` 윈도우는 `LAUNCH_CMD`(codex app-server + bridge 데몬), `codex` 윈도우는 같은 app-server에 터미널 화면(TUI)을 붙여 실시간 관전·개입.
Expand Down Expand Up @@ -276,15 +282,15 @@ Claude Code + Codex 에이전트가 공존하게 하는 규칙. `bot-roster.yaml
- **직통 채널은 멘션 규칙 면제**(`require_mention: false`).
- **회의 = 전용 스레드**: 봇 2개↑·10분↑·안건 있음 중 2개 충족 시 전용 스레드 신설, 본 채널엔 안내만. 단발 relay/ACK는 본문 유지.
- **세션 시작 주입**: 단일 렌더러(`roster-inject.py`)가 같은 좌표·규칙을 Claude Code 봇(세션 init 훅)과 Codex 봇(`~/.codex/hooks.json`) 양쪽에 주입.
- **디스코드 답장 규칙(정적, AGENTS.md — 매 턴 아님)**: 각 턴은 `<channel chat_id="…" message_id="…">`로 들어옴 → `mcp__discord__reply(chat_id, reply_to=message_id)`로 답장. 페르소나·볼트 규율은 `SOUL.md`/`AGENTS.md`가 자동 로드되므로 항상 적용.
- **디스코드 답장 규칙(정적, AGENTS.md — 매 턴 아님)**: 각 턴은 `<channel chat_id="…" message_id="…">`로 들어옴 → `mcp__discord__reply(chat_id, reply_to=message_id)`로 답장. 페르소나·볼트 규율은 정본 `AGENTS.md`가 자동 로드되므로 항상 적용.

---

## 5. Claude Code ↔ Codex 마이그레이션 규칙

| 항목 | Claude Code | Codex 대응 |
|---|---|---|
| 페르소나·규칙 로드 | `CLAUDE.md` + 세션 시작 훅 | `project_doc_fallback_filenames`로 `AGENTS.md`/`SOUL.md` |
| 페르소나·규칙 로드 | `CLAUDE.md` + 세션 시작 훅 | 정본 `AGENTS.md`; `SOUL.md`는 `AGENTS.md`가 없을 때만 구형 fallback |
| 들어오는 디스코드 이벤트 | `claude --channels` 내장 | `bot.py` bridge → `turn/start` |
| 나가는 응답 | `mcp__discord__reply` 도구 | 동일(discord 플러그인=codex MCP) |
| 도구 승인 | 권한 모드 | `approvalPolicy` + bridge 자동수락 |
Expand Down Expand Up @@ -320,6 +326,6 @@ Claude Code + Codex 에이전트가 공존하게 하는 규칙. `bot-roster.yaml
- ✅ Progressive-disclosure **rules 시스템**(규칙을 다 넣지 않고 상황별 참조 — context bloat 방지) — 컨벤션 동봉, [docs/rules-system.md](docs/rules-system.md).
- ✅ **가역 메모리 정리(지우지 않고 옮김)** (`scripts/memory_dreaming.py`) — 안 쓰는 메모리를 작업공간 밖 보관소로 옮기고 명령 한 줄로 체크섬 검증 복원. 9칸 전부 같은 기준표 **Codex 메모리 칸 포함**(`~/.codex/memories`, cold subdir env 설정), 보수적(자동이동 게이트·애매하면 사람검토)·기준 자기보정·주1회 강제. 쉬운 설명: [docs/memory-dreaming.md](docs/memory-dreaming.md).
- ✅ **회의 watchdog** (`scripts/meeting_watchdog.py`) — **회의마다 권장 (감시 봇 1개 초대, 첫 dispatch 전에 가동)**. 회의 스레드 신설 시 YAML 강제 ~5분 진행 점검(메인테이너 vault 는 ~3분 운영), 목표+전체 작업 완료 시에만 자동 종료(Claude `/goal` 응용), fail-closed = 살아있는 회의 절대 잘못 종료 안 함. [docs/05-meeting-thread-protocol.md](docs/05-meeting-thread-protocol.md) §2.3 + [rules/meeting-protocol.md](rules/meeting-protocol.md) §5 와 짝.
- ⚙️ **설정 가이드**(AGENTS.md · soul.md · rules · Skills 2.0 체크리스트) — [docs/SETUP-CONFIG-GUIDE.md](docs/SETUP-CONFIG-GUIDE.md).
- ⚙️ **설정 가이드**(정본 AGENTS.md + SOUL v2 capsule · rules · Skills 2.0 체크리스트) — [docs/SETUP-CONFIG-GUIDE.md](docs/SETUP-CONFIG-GUIDE.md).

라이선스: 레포 참조. 본인이 통제하는 머신 + 신뢰 가능한 비공개 디스코드 서버에서만 사용.
18 changes: 12 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ npx github:treylom/ThisCodex doctor --non-interactive # (terminal) verificatio
|---|---|---|
| Codex CLI as a persistent Discord bot | ✅ working | `codex app-server` (headless) + Python bridge daemon (`bot.py`) + discord.py |
| Multi-client same-thread (watch/steer the bot's conversation from a TUI) | ✅ working | `codex resume <thread-id> --remote ws://…` against the same app-server |
| Persona / vault rules auto-loaded | ✅ working | `~/.codex/config.toml` → `project_doc_fallback_filenames = ["SOUL.md","AGENTS.md"]` |
| Persona / vault rules auto-loaded | ✅ working | canonical `BOT_WD/AGENTS.md`; `SOUL.md` is configured only as a legacy fallback when `AGENTS.md` is absent |
| Cross-bot addressing + meeting discipline | ✅ working | `bot-roster.yaml` SoT injected at SessionStart |
| YOLO (full-access) execution | ✅ working | `thread/start` **and** `thread/resume` both send `sandbox:"danger-full-access"`, `approvalPolicy:"never"` |
| Image generation | ✅ working | codex built-in `image_gen.imagegen` tool |
Expand Down Expand Up @@ -228,7 +228,8 @@ zero-config default.

### 3.2 `~/.codex/config.toml`
```toml
project_doc_fallback_filenames = ["SOUL.md", "AGENTS.md"]
# AGENTS.md is Codex's canonical filename; SOUL.md is legacy fallback only.
project_doc_fallback_filenames = ["SOUL.md"]
project_doc_max_bytes = 65536

[mcp_servers.discord]
Expand All @@ -239,7 +240,12 @@ DISCORD_STATE_DIR = "~/.claude/channels/discord-<botname>"
```

### 3.3 Bot working directory
Put `SOUL.md` (persona) and `AGENTS.md` (rules — including the static Discord-reply rule, see §4) in the bot WD. They are auto-loaded every thread; **do not** re-inject persona text per turn.
Put one canonical `AGENTS.md` in the bot WD. It contains the SOUL v2 persona
capsule and the static Discord-reply rule (see §4), and points to the rules
router. Codex selects one instruction file per directory; a same-directory
`SOUL.md` is not loaded alongside `AGENTS.md`. `SOUL.md` remains a legacy
fallback only when `AGENTS.md` is absent. **Do not** re-inject static persona
text per turn.

### 3.4 Run it (the bridge is what grants access)
A 2-window tmux launcher (`scripts/launch.sh`): window `infra` runs your
Expand Down Expand Up @@ -387,7 +393,7 @@ These are the rules that make Claude Code + Codex agents coexist. They live in `
- **Direct channels are exempt** from the mention rule (`require_mention: false`).
- **Meetings = dedicated threads**: any task with ≥2 bots, ≥10 min, or an agenda (2-of-3) gets its own thread; the main channel only gets a redirect. One-shot relays/ACKs stay inline.
- **SessionStart injection**: a single renderer (`roster-inject.py`) feeds the same coordinates + rules into both Claude Code bots (via the session-init hook) and Codex bots (via `~/.codex/hooks.json`).
- **Discord-reply rule (static, in AGENTS.md — not per turn)**: each turn arrives as `<channel chat_id="…" message_id="…" …>`; reply with `mcp__discord__reply(chat_id, reply_to=message_id)`. Persona/vault discipline is always on because `SOUL.md`/`AGENTS.md` are project-doc auto-loaded.
- **Discord-reply rule (static, in AGENTS.md — not per turn)**: each turn arrives as `<channel chat_id="…" message_id="…" …>`; reply with `mcp__discord__reply(chat_id, reply_to=message_id)`. Persona/vault discipline is always on because the canonical `AGENTS.md` is auto-loaded.

---

Expand All @@ -397,7 +403,7 @@ Bringing a Claude Code agent's behavior to Codex (and back):

| Concern | Claude Code | Codex equivalent |
|---|---|---|
| Persona/rules load | `CLAUDE.md` + SessionStart hook | `AGENTS.md`/`SOUL.md` via `project_doc_fallback_filenames` |
| Persona/rules load | `CLAUDE.md` + SessionStart hook | canonical `AGENTS.md`; `SOUL.md` only as a legacy fallback when `AGENTS.md` is absent |
| Inbound Discord event | built into `claude --channels` | `bot.py` bridge → `turn/start` |
| Outbound | `mcp__discord__reply` tool | identical (discord plugin as codex MCP) |
| Tool approvals | permission modes | `approvalPolicy` + bridge auto-accept elicitation |
Expand Down Expand Up @@ -433,6 +439,6 @@ When upstream exposes `computer_use` to the CLI, **do not** pipe untrusted Disco
- ✅ Progressive-disclosure **rules system** (no context bloat — situational rule routing) — convention shipped, see [docs/rules-system.md](docs/rules-system.md).
- ✅ **Reversible memory archival** (`scripts/memory_dreaming.py`) — *move-not-delete* cleanup to out-of-WD cold storage, one-command checksum-verified restore; one tier-agnostic rubric across all tiers **incl. the Codex memory tier** (`~/.codex/memories`, env-configurable cold subdir); conservative (auto-archive gated, ambiguous → human review), criteria self-correct from restores; weekly-enforced. Plain-language: [docs/memory-dreaming.md](docs/memory-dreaming.md).
- ✅ **Meeting watchdog** (`scripts/meeting_watchdog.py`) — **recommended on every meeting (invite one watchdog bot per meeting, start before the first dispatch)**. On meeting-thread creation, YAML-enforced ~5-min progress check (maintainer's vault runs ~3 min); self-terminates only when goal AND all tasks complete (models Claude Code `/goal`); fail-closed = never falsely terminate a live meeting. Pairs with [docs/05-meeting-thread-protocol.md](docs/05-meeting-thread-protocol.md) §2.3 + [rules/meeting-protocol.md](rules/meeting-protocol.md) §5.
- ⚙️ **Config guide** (AGENTS.md · soul.md · rules · Skills 2.0 checklist) — [docs/SETUP-CONFIG-GUIDE.md](docs/SETUP-CONFIG-GUIDE.md).
- ⚙️ **Config guide** (canonical AGENTS.md + SOUL v2 capsule · rules · Skills 2.0 checklist) — [docs/SETUP-CONFIG-GUIDE.md](docs/SETUP-CONFIG-GUIDE.md).

License: see repo. Use on machines you control, with trusted private Discord servers only.
32 changes: 30 additions & 2 deletions bin/thiscodex.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
} from '../scripts/lib/state.mjs';
import { verifyStep } from '../scripts/lib/doctor.mjs';
import { applySkillInstall, marketplaceHint, patchCodexConfig } from '../scripts/lib/apply.mjs';
import { aliasBlock, materializeBotFiles } from '../scripts/lib/materialize.mjs';
import { aliasBlock, materializeBotFiles, migrateIdentity } from '../scripts/lib/materialize.mjs';
import { promptForStep } from '../scripts/lib/prompts.mjs';
import {
appendAutomationAudit,
Expand Down Expand Up @@ -45,7 +45,7 @@ import {
} from '../scripts/lib/browser-tools.mjs';

const args = process.argv.slice(2);
const command = ['init', 'doctor', 'smoke', 'discord-thread', 'browser-e2e', 'automation-flow', 'automation-attempt', 'automation-gate'].includes(args[0]) ? args.shift() : 'init';
const command = ['init', 'doctor', 'smoke', 'discord-thread', 'browser-e2e', 'automation-flow', 'automation-attempt', 'automation-gate', 'migrate-identity'].includes(args[0]) ? args.shift() : 'init';
const has = flag => args.includes(flag);
const arg = name => {
const found = args.find(a => a.startsWith(`${name}=`));
Expand Down Expand Up @@ -251,6 +251,34 @@ if (command === 'discord-thread') {
process.exit(output.ok ? 0 : 2);
}

if (command === 'migrate-identity') {
let output;
try {
if (has('--preview') && has('--apply')) throw new Error('migrate-identity accepts either --preview (the default) or --apply, not both');
const installState = loadInstallState();
const bot = arg('--bot-wd') || installState.confirmed_bot_wd;
if (!bot) throw new Error('migrate-identity requires --bot-wd <path> or a confirmed guided-install BOT_WD');
const rollback = has('--rollback');
output = migrateIdentity({
repo: resolve(arg('--repo-root') || repoRoot),
bot: resolve(bot),
apply: has('--apply'),
rollback,
});
output.next_command = output.ok && output.mode === 'preview'
? rollback
? 'Review the exact candidate path, then rerun with --rollback --apply to remove only the unchanged staged candidate.'
: 'Review the exact source, current, target, backup, and candidate paths, then rerun with --apply to stage without overwriting the active identity file.'
: output.ok && output.action === 'backed_up_then_staged_v2_candidate_no_overwrite'
? `Review AGENTS.md.v2 against ${output.current_kind === 'legacy_soul' ? 'SOUL.md' : 'AGENTS.md'}; the original remains active and the backup/receipt are the rollback point.`
: '';
} catch (error) {
output = { ok: false, code: 'identity_migration_error', action: 'none', message: error.message };
}
console.log(JSON.stringify(output, null, 2));
process.exit(output.ok ? 0 : 2);
}

const automationPolicy = getAutomationPolicy();

if (command === 'browser-e2e') {
Expand Down
Loading
Loading