English | 한국어
A two-way harness where Codex mates challenge your plans and Codex workers build them. Meight is built for an LLM agent that designs collaboratively, delegates, steers, reviews, and signs off with evidence. Official
openai-codexPython SDK underneath. CLI:meight.
Most bridges are built for a human watching a terminal: tmux panes, dashboards, stdout scraping. Meight is built for the agent itself — a dispatcher that starts hidden Codex sessions, reads compact disk digests, steers a live turn, answers structured questions, and keeps final reports small enough to make user-facing decisions without drowning in implementation detail.
The core idea is that a frontier model wasted as a silent executor is capability left on the table. So meight exposes two postures — two Codex session contracts:
- A mate (
--mode mate) is an independent thinking partner. It joins blind/anchored design, diagnoses, and reviews named artifacts with independent judgment — its contract says challenge the dispatcher, agreement is not the goal. A reviewer may surface both material problems and better directions. - A worker (
--mode worker) is a team implementer. It owns code, tests, verification, and self-review, surfaces observations and better directions instead of executing silently, and escalates dispatcher-sign-off gates (security-sensitive, public API contracts, data migration, money paths, frozen review chains) before acting. The dispatcher decides whether to run a separate external review.
Mate and worker name the session contract, not the model; the mode picks the
contract, and --model picks the brain. The dispatcher keeps direction,
arbitration, integration, and final sign-off; nothing merges on a mate's or
worker's word alone.
dispatcher agent <-> Codex mate(s) / worker(s)
(what and why) (challenge / implement)
| ^
|-- dispatch + brief ---|
|
|<- QUESTION / result
|-- reply / steer / design / review
|
v
global daemon -- official openai-codex SDK -- per-worker codex app-server
status.json · events.log · result.md
Meight provides two session postures, not a mandatory development pipeline. Avoiding overengineering comes first: the dispatcher chooses only the design, review, implementation, and verification gates justified by the task's failure cost, then records that choice in one line.
Blind or anchored design can clarify a real direction fork. Artifact review is
an independent judgment over the intended outcome and constraints; it may
surface material observations and better directions even when they were not
explicitly requested. One mate is the
default, and a second unanchored read is useful only when it can change the
decision. A worker's done is still only a claim. Sign-off combines any
requested verdict with verification evidence.
The included operator-policy template starts workers on grok high. When a
brief needs repository-understanding judgment, the dispatcher explicitly
selects --model sol. A complete brief may still select --model luna, which
resolves to luna max with Fast. Mate/review stays on sol medium unless the
dispatcher selects --model grok or --model grok --effort xhigh. Failure
cost remains an independent gate for raising the brain or adding review. When
work is hard, the template adds a stage instead of a larger worker — a sol
mate plan is frozen, then handed to a worker with a complete brief. These model
and money-path gates are explicitly adjustable operator policy, not meight
interface requirements.
Effort follows the same economics: a selected luna runs max, which buys a
measured four Index points over xhigh for a quarter more cost. Selected
grok runs high; xhigh is the explicit mate/review option and the catalog
ceiling. Worker sol stays at medium when selected. Formal or high-cost
review may use sol high or grok xhigh; design uses those higher efforts
only when genuinely hard and after one user confirmation. sol never runs
xhigh. Grok has no Fast or max/ultra.
The official openai-codex Python SDK talks to codex app-server directly and
exposes steering, interrupting, streaming, output schemas, and thread control as
APIs. Meight uses one SDK runtime per active worker, then releases it when the
worker finishes so MCP subprocesses and file descriptors do not linger.
Compared with tmux/exec wrappers:
| tmux/exec bridges | MCP wrappers | Meight | |
|---|---|---|---|
| Parallel sessions | 1 process per worker | blocking tool calls | one SDK runtime per active worker |
| Mid-turn steering | attach/type or kill+resume | no | meight steer |
| Progress observation | scrape stdout | no | disk digest, pull on demand |
| Two-way conversation | no | no | structured QUESTION: -> exit 3 -> reply |
| Result delivery | scrape | tool return | exit-code contract + result files |
| Result format | no | wrapper-specific | plain text result.md |
| Session contracts | no | no | --mode mate|worker, harness-injected |
And because every judgment lands on disk — digests, decisions, preferences, lessons — the pairing gets more personal over time: the dispatcher learns which questions its human wants to see, and which ones it is trusted to answer itself.
Requirements: Codex CLI installed and authenticated, Python >= 3.10.
git clone https://github.com/keepitmello/claude-codex-meight
cd claude-codex-meight
./install.sh # creates .venv + ~/.local/bin/meightFor real work, use supervised dispatch from any git repo. Meight uses one global
daemon by default ($MEIGHT_HOME, $XDG_STATE_HOME/meight, or ~/.meight)
while isolating worker state per repo under repos/<repo-key>/.
meight dispatch impl-1 --mode worker --timeout 300 \
--brief-file - --cwd ~/my-repo <<'EOF'
Implement X in src/foo.py. Existing pattern: see src/bar.py:42.
Verify with: pytest tests/test_foo.py.
Report changed files, verification, remaining P1s, risks, and evidence artifact.
EOF
# exit 0=completed · 2=failed/interrupted/runtime-lost · 3=replyable question · 4=daemon dead · 1=checkpoint timeoutWhile that call blocks, a second terminal can watch the worker talk. Run it bare and pick from a menu when you do not remember the name:
$ meight watch
# NAME STATE MODE ELAPSED FILES TOKENS CURRENT
── active ──
1 impl-watch running worker 0s files:2 in:48210 ... commandExecution: pnpm typecheck:be (8s)
2 review-menu running mate 0s files:0 in:12040 ... reasoning (2s)
── idle — finished, or waiting on you ──
3 design-auth needs_input mate 0s files:0 in:9100 ... -
select 1-3 (enter to quit):meight watch impl-1
# ── 2026-08-05T02:01:59+09:00 ──
# 실패 원인을 녹화 흐름으로 좁혔습니다. dev 서버가 첫 `/app` 요청 때 Fast
# Refresh를 일으켜 탭 선택을 되돌린 것이어서, 서버 readiness에 warm-up을 넣었습니다.
# ▸ commandExecution: pnpm test (12s) <- footer while the worker is quietMessages stream as the worker writes them, in full: status.json keeps only a
500-character tail and events.log a 150-character summary. This is for a
human. An orchestrating agent still pulls digests, because streaming worker
output into its context costs tokens linearly with runtime.
On exit 1, the worker is still running. Inspect once, steer if needed, then
run the same dispatch again to reattach; no separate polling command is needed:
meight status impl-1
meight steer impl-1 "Stop refactoring the helper; only fix the bug."
meight dispatch impl-1 --mode worker --timeout 300On terminal exits, read the text result:
meight result impl-1The worker asked a replyable question (exit 3)? The same target/kind is also
visible in status.json and meight status.
meight reply impl-1 --brief "Use config-a.json and keep the legacy field."Blind design goes to a mate instead — advisory and collaborative:
meight dispatch design-auth --mode mate \
--cwd ~/my-repo --brief-file - <<'EOF'
We need to choose an auth-token refresh design.
Constraints:
- No user-visible logout regression.
- Existing token storage is in src/auth/store.ts.
Options:
- Option A: refresh before each protected request when expiry is near.
- Option B: centralize refresh in the API client on 401.
Give the best-supported design, the strongest case against it, and the evidence
that would settle remaining uncertainty. No code changes.
EOFOne-shot dispatch is available when a separately supervised session adds no value:
meight dispatch tiny-1 --mode worker --sandbox ro \
--brief "Check whether README mentions LICENSE."Computer Use app-access is enabled by default for each meight session. Other MCP approvals remain unchanged.
Sessions do not guess and do not silently comply. When blocked — or when they
see a better direction — they end the turn with a structured question the
daemon promotes to exit 3:
QUESTION:
TARGET: dispatcher | user
KIND: scope | ux | priority | risk | irreversible | acceptance | missing-info | better-direction | technical
<question + options + recommendation>
TARGET says who must decide; KIND says why. A middle-layer agent answers
dispatcher-owned questions with meight reply and escalates user-owned ones
(scope, UX, risk appetite, irreversible actions) verbatim.
Routing is impact-based: an answer that starts a new worker, phase,
plan/addendum, review identity beyond the initial round's optional fresh read
or a preauthorized re-review, expensive
rerun, materially different method, or additional repair after the campaign
cap is user-owned even when labeled technical. Worker names and fresh review
identities do not reset the cap.
Three plain-file ledgers make the dispatch loop improve with use:
- Decision records (
<repo>/decisions/). Every direction-setting fork resolved by two independent designs leaves a record: both positions, where they split, and what settled it. Later sessions audit the why; settled questions stay settled. - Preference ledger (
<daemon-home>/notes/preferences.md). When the human answers aTARGET: userquestion, the answer is recorded. The dispatcher checks the ledger before escalating, so each class of question reaches the human once — only irreversible and risk calls are always re-confirmed. - Lessons (
<daemon-home>/notes/lessons.md). Recurring review findings and operational mistakes become one-line lessons, promoted into brief templates when they repeat. Per-run records can carry the mode, one-line gate choice, reroute reason, and post-sign-off defects — enough to tune routing from outcomes without turning measurement into ceremony.
None of this is a new subsystem — just files plus doctrine, defined in
skills/meight/SKILL.md.
Judgments persist on disk rather than in model memory, so the personalization
survives context compaction, fresh sessions, and even model swaps.
For real work, run dispatch --timeout as the background shell call. The agent
wakes at completion, question, failure, daemon death, or checkpoint timeout.
If the checkpoint expires, the worker keeps running and the same dispatch
command reattaches to it.
Bash(command: "meight dispatch review-1 --mode mate --timeout 300 --brief-file - <<'EOF' ... EOF",
run_in_background: true)
-> checkpoint exit 1
-> meight status review-1
-> healthy: same dispatch again · drifting: meight steer review-1 "..."
A drop-in Claude orchestrator prompt ships as CLAUDE.md. The
root AGENTS.md guides work on this repository; the installable
global Codex policy lives at
bindings/codex/AGENTS.md. The full dispatcher-facing
skill is skills/meight/. The session contracts are
skills/meight-mate/ and
skills/meight-worker/, with their shared protocol in
skills/meight-common/.
Runtime-specific prompt sources live under bindings/. Claude
uses bindings/claude/tech-lead.md to route
review through meight. Codex uses the native
meight,
codex-reviewer, and
codex-discusser skills,
plus the read-only
reviewer agent definition. Copy the
global policy to ~/.codex/AGENTS.md, or merge it when that file already has
local rules. Link the three Codex skill directories into ~/.codex/skills/,
link or copy the agent definition to ~/.codex/agents/reviewer.toml, and merge
only the reviewer stanza from
config-fragment.toml into the local
Codex config. Keep authentication and MCP configuration local.
The default dispatcher is a Claude Code session. A Codex app session uses its native binding and collaboration tools while preserving the same meight contracts — one protocol, two dispatcher runtimes. The reviewer and discusser are Codex-native skills, not shared session contracts.
- Exit codes are the API.
0done,2failed/interrupted/runtime-lost,3question,4daemon gone,1checkpoint timeout. - Names, not session IDs. Sessions are addressed as
review-1, including follow-ups. Names are 1-128 ASCII letters/digits/._-, starting with a letter or digit; the CLI and daemon both reject path syntax. - Sparse checkpoints, not busy polling.
dispatch --timeoutis a wake-up dial; it does not kill the worker, and re-running it reattaches. - Status is pre-digested.
statusreturns mode, current item, changed files, needs-input target/kind, and last-message tail. - Policy cannot be forgotten. Mode, mode-skill loading, and the shared
contract are injected by the harness —
--modeis a required flag with a teaching error, validated at the daemon boundary too, so a stale CLI or a raw socket client gets the same contract. - Results survive on disk.
result.mdcontains the worker's text result. - Briefs go through stdin. Multi-line briefs avoid shell quoting traps.
| Command | What it does |
|---|---|
meight dispatch <name> --mode mate|worker [--target mac|desktop] [opts] |
One-shot: auto-start or reattach, then poll and print the result. mac is the default. desktop uses wy-server, requires a clean commit plus configured remote repo mapping, and never falls back to Mac. |
meight reply <name> --brief ... [--model M] [--effort E] [--fast|--no-fast] |
One-shot answer to a replyable question; inherits mode and omitted turn settings, applies explicit turn overrides, and prints the latest result. |
meight follow <name> --brief ... [--model M] [--effort E] [--fast|--no-fast] |
Low-level: new turn on the same live thread; inherits mode and omitted turn settings, while explicit overrides become the defaults for later turns. |
meight result <name> |
Print result.md. |
meight watch [name] [--all] [--include-archived] [--from-start] [--tail N] |
Stream what a worker says, live, in a second terminal while dispatch blocks. Full message text, unmodified. Read-only, no daemon needed. With no name it attaches to the only active worker, or offers a numbered menu when there is a choice; --all interleaves them with a name prefix. Ctrl-C leaves the worker running. |
meight status [name] [--json] [--all-repos] [--archived | --all] |
Pull digest. With no name, the default view includes active workers and terminal workers from the last 6 hours; --archived shows older terminal rows and --all shows both. Table includes MODE; legacy rows with old role or long-form mode values remain readable. Reads disk. |
meight steer <name> "text" |
Inject instruction into the running turn. |
meight interrupt <name> |
Cancel the turn. An interrupt that arrives while a worker is still starting — or while a reply turn is being opened — is recorded, and aborts the turn the moment it would commit. |
meight list / daemon / ping / shutdown / launchd |
Low-level support commands. |
Common options:
--mode mate|workeris required ondispatchwhen opening a new session. Legacy namesdesign,collab,collaborative,review(→ mate) anddelegate,delegated(→ worker) are accepted aliases. Mate is the thinking-partner contract; worker is team implementation with self-review and a dispatcher-owned external-review choice.--cwdsets the worker workdir. Use separate git worktrees for overlapping file scopes.--target mac|desktopselects where the runtime runs. Desktop changes are returned as hash-verified artifacts under the worker directory and are never applied to the caller's checkout automatically.--sandbox ws|ro|fulluses the mode default below.--model luna|sol|terraaccepts the short aliases; full model strings pass through unchanged.--effort low|medium|high|xhigh|ultra|maxuses the selected model's default forsol/luna(medium/max); otherwise it uses the mode default below.--fastselects priority service tier and--no-fastdisables it. An omitted Fast flag follows the selected model's default, so explicit--model lunaresolves to Fast on while explicit Fast flags always win. Onfollow/reply, omitting--model,--effort, and Fast flags inherits the worker's current values; an explicit override applies to that new turn and becomes the value inherited by later turns.- Workers use ephemeral threads so they are not added to Codex's stored thread
listings.
thread_source=subagentis retained only as source metadata.
Omitted dispatch settings resolve in the CLI before the request is
sent:
| Mode | Model | Effort | Fast | Sandbox |
|---|---|---|---|---|
mate |
sol |
medium |
off | full |
worker |
grok |
high |
off | full |
An explicit --model sol|luna|grok without --effort reselects that model's
effort default; when Fast is omitted, the selected model's Fast default is
reselected as well. Thus --model luna yields luna max with Fast, and
--model grok yields grok high with Fast off. Explicit --fast/--no-fast
always wins.
Neither posture enforces a sandbox: read-only is brief-driven policy (the mate
contract defaults to not modifying repository files), and --sandbox remains
for manual selection.
Standard is silent: specify only deviations. The table lives in meight.py as
deliberately simple code-only operator policy; there is no config-file or
environment override layer. New-session dispatch output echoes every resolved value with
(default) or (set) provenance.
Worker state lives in
<daemon-home>/repos/<repo-key>/workers/<name>/: brief.md, status.json,
events.log, and result.md. Terminal workers keep disk artifacts but release
their SDK runtime immediately. A final structured QUESTION: also releases its
runtime and remains as a dormant disk row. reply/follow opens a fresh
ephemeral thread and injects a bounded handoff from saved brief, result, and
recent events, including after daemon restart or in-memory worker GC.
The daemon derives and verifies the repo key and state home instead of trusting
socket request paths. Its home, repos/, and repo/worker state directories are
owner-only (0700), worker state paths may not be symlinks, and meight.sock
is 0600. Socket requests are bounded to 1 MiB. No process-wide umask is set,
so worker-created repository files keep the worker process's normal modes.
Terminal artifacts are retained for 30 days by default. Set
MEIGHT_SESSION_RETENTION_SEC to another non-negative number of seconds, or
0 to disable disk pruning. Cleanup runs off the accept loop at most hourly,
never removes active, replyable, malformed, symlinked, or currently registered
workers, and uses immutable terminal_at (updated_at only for legacy rows).
After a daemon crash/restart, orphaned active rows become
failed/runtime_lost_detail. Final questions and terminal workers remain
continuable through the same bounded artifact handoff.
The default status/list view keeps active workers and terminal workers from the
last 6 hours visible. Older terminal rows remain on disk and continuable through
follow; inspect them with --archived, or combine both views with --all.
The CLI fails closed before dispatch when meight ping does not advertise the
current capability (desktop1). Every wire start/follow request carries the epoch,
and every successful response atomically echoes normalized mode, target,
runtime, and epoch. The CLI validates all four, so even a same-token daemon swapped mid-handshake cannot
silently use an old contract. Drain and restart manually:
- Inspect
meight list --all-repos --json; wait until no session across any repo has a live turn (starting,running, or tool-sourcedneeds_input). A finalQUESTION:row is dormant and does not block migration. - Run non-force
meight shutdown. If it refuses, finish draining; do not use--forcefor this migration. - Branch on LaunchAgent state. If loaded, run
meight launchd install --loadand verify its boundedbootout --waittransfer selects the fresh daemon; if not loaded, start the daemon normally. - Confirm
meight pingshowscapabilities=desktop1, then verify the new daemon PID and socket identity. - Run a throwaway
--mode workersmoke (brief-directed read-only) and verify status mode plusmeight-workerand common preamble paths. - Run a throwaway
--mode matesmoke and verifymode=mateplusmeight-mateand common preamble paths. - Resume real dispatches only after every smoke passes.
- Meight inherits your
~/.codex/config.tomlfor model, MCP servers, and auth. Ifcodexworks in your terminal,meightworks. - Meight uses the current system
codexexecutable rather than the SDK's bundled runtime. SetMEIGHT_CODEX_BINonly when an explicit executable override is needed. - Sessions start as non-persisted Codex threads:
thread_source=subagent,thread_ephemeral=true. The source value is metadata;ephemeral=trueis what prevents app/session-history accumulation. - Foreground
meight daemonexits afterMEIGHT_IDLE_TIMEOUT_SECseconds with no active workers by default. Manageddispatchauto-start and LaunchAgent starts disable idle shutdown; verify idle and retention values withmeight ping. - The LaunchAgent uses crash-only supervision (
SuccessfulExit=false). A clean shutdown stays stopped. Auto-start useslaunchctl kickstartwhen the job is loaded and neverkickstart -k; direct detached startup is only the fallback when no job is loaded.launchd install --loaddrains the old daemon without force, waits for its acknowledged PID/socket exit, runs boundedlaunchctl bootout --waiton a loaded job, bootstraps the plist, and requires a fresh daemon PID/socket identity whose PID also matches the running job reported by launchd. Ambiguouslaunchctlresults or an unhealthy daemon that still holds the singleton lock fail closed. If the published socket is deleted or replaced, the daemon exits nonzero so launchd can recreate it. openai-codexis pinned (0.144.4). When bumping the SDK or Codex CLI, re-run the verification suite inSPEC.md.- Design details, state machine, hardening history, and lifecycle caveats live
in
ARCHITECTURE.md. Full dispatcher protocol lives inskills/meight/SKILL.md. The earlier pipeline design retrospective — including the day it was designed by running itself — is indocs/2026-07-14-v3-pipeline-retrospective.md.
MIT
