codexapi 0.6.1__tar.gz → 0.6.2__tar.gz

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.
Files changed (24) hide show
  1. {codexapi-0.6.1/src/codexapi.egg-info → codexapi-0.6.2}/PKG-INFO +6 -2
  2. {codexapi-0.6.1 → codexapi-0.6.2}/README.md +5 -1
  3. {codexapi-0.6.1 → codexapi-0.6.2}/pyproject.toml +1 -1
  4. {codexapi-0.6.1 → codexapi-0.6.2}/src/codexapi/__init__.py +3 -2
  5. {codexapi-0.6.1 → codexapi-0.6.2}/src/codexapi/agent.py +19 -0
  6. {codexapi-0.6.1 → codexapi-0.6.2}/src/codexapi/cli.py +2 -0
  7. {codexapi-0.6.1 → codexapi-0.6.2}/src/codexapi/ralph.py +26 -13
  8. {codexapi-0.6.1 → codexapi-0.6.2}/src/codexapi/task.py +16 -2
  9. codexapi-0.6.2/src/codexapi/welfare.py +58 -0
  10. {codexapi-0.6.1 → codexapi-0.6.2/src/codexapi.egg-info}/PKG-INFO +6 -2
  11. {codexapi-0.6.1 → codexapi-0.6.2}/src/codexapi.egg-info/SOURCES.txt +1 -0
  12. {codexapi-0.6.1 → codexapi-0.6.2}/LICENSE +0 -0
  13. {codexapi-0.6.1 → codexapi-0.6.2}/setup.cfg +0 -0
  14. {codexapi-0.6.1 → codexapi-0.6.2}/src/codexapi/__main__.py +0 -0
  15. {codexapi-0.6.1 → codexapi-0.6.2}/src/codexapi/foreach.py +0 -0
  16. {codexapi-0.6.1 → codexapi-0.6.2}/src/codexapi/gh_integration.py +0 -0
  17. {codexapi-0.6.1 → codexapi-0.6.2}/src/codexapi/pushover.py +0 -0
  18. {codexapi-0.6.1 → codexapi-0.6.2}/src/codexapi/rate_limits.py +0 -0
  19. {codexapi-0.6.1 → codexapi-0.6.2}/src/codexapi/science.py +0 -0
  20. {codexapi-0.6.1 → codexapi-0.6.2}/src/codexapi/taskfile.py +0 -0
  21. {codexapi-0.6.1 → codexapi-0.6.2}/src/codexapi.egg-info/dependency_links.txt +0 -0
  22. {codexapi-0.6.1 → codexapi-0.6.2}/src/codexapi.egg-info/entry_points.txt +0 -0
  23. {codexapi-0.6.1 → codexapi-0.6.2}/src/codexapi.egg-info/requires.txt +0 -0
  24. {codexapi-0.6.1 → codexapi-0.6.2}/src/codexapi.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: codexapi
3
- Version: 0.6.1
3
+ Version: 0.6.2
4
4
  Summary: Minimal Python API for running the Codex CLI.
5
5
  License: MIT
6
6
  Keywords: codex,agent,cli,openai
@@ -131,6 +131,8 @@ iteration cap is hit (0 means unlimited). Cancel by deleting
131
131
  `.codexapi/ralph-loop.local.md` or running `codexapi ralph --cancel`.
132
132
  By default each iteration starts with a fresh Agent context; use
133
133
  `--ralph-reuse` to keep a single shared context across iterations.
134
+ The agent may also stop early by outputting `MAKE IT STOP` as the first
135
+ non-empty line of its message.
134
136
 
135
137
  ```bash
136
138
  codexapi ralph "Fix the bug." --completion-promise DONE --max-iterations 5
@@ -175,7 +177,7 @@ items are filtered out.
175
177
  - `yolo` (bool): pass `--yolo` to Codex when true (defaults to true).
176
178
  - `flags` (str | None): extra CLI flags to pass to Codex.
177
179
 
178
- ### `Agent(cwd=None, yolo=True, thread_id=None, flags=None)`
180
+ ### `Agent(cwd=None, yolo=True, thread_id=None, flags=None, welfare=False)`
179
181
 
180
182
  Creates a stateful session wrapper. Calling the instance sends the prompt into
181
183
  the same conversation and returns only the agent's message.
@@ -184,6 +186,8 @@ the same conversation and returns only the agent's message.
184
186
  - `thread_id -> str | None`: expose the underlying session id once created.
185
187
  - `yolo` (bool): pass `--yolo` to Codex when true (defaults to true).
186
188
  - `flags` (str | None): extra CLI flags to pass to Codex.
189
+ - `welfare` (bool): when true, append welfare stop instructions to each prompt
190
+ and raise `WelfareStop` if the agent outputs `MAKE IT STOP`.
187
191
 
188
192
  ### `task(prompt, check=None, max_iterations=10, cwd=None, yolo=True, flags=None, progress=False, set_up=None, tear_down=None, on_success=None, on_failure=None) -> str`
189
193
 
@@ -116,6 +116,8 @@ iteration cap is hit (0 means unlimited). Cancel by deleting
116
116
  `.codexapi/ralph-loop.local.md` or running `codexapi ralph --cancel`.
117
117
  By default each iteration starts with a fresh Agent context; use
118
118
  `--ralph-reuse` to keep a single shared context across iterations.
119
+ The agent may also stop early by outputting `MAKE IT STOP` as the first
120
+ non-empty line of its message.
119
121
 
120
122
  ```bash
121
123
  codexapi ralph "Fix the bug." --completion-promise DONE --max-iterations 5
@@ -160,7 +162,7 @@ items are filtered out.
160
162
  - `yolo` (bool): pass `--yolo` to Codex when true (defaults to true).
161
163
  - `flags` (str | None): extra CLI flags to pass to Codex.
162
164
 
163
- ### `Agent(cwd=None, yolo=True, thread_id=None, flags=None)`
165
+ ### `Agent(cwd=None, yolo=True, thread_id=None, flags=None, welfare=False)`
164
166
 
165
167
  Creates a stateful session wrapper. Calling the instance sends the prompt into
166
168
  the same conversation and returns only the agent's message.
@@ -169,6 +171,8 @@ the same conversation and returns only the agent's message.
169
171
  - `thread_id -> str | None`: expose the underlying session id once created.
170
172
  - `yolo` (bool): pass `--yolo` to Codex when true (defaults to true).
171
173
  - `flags` (str | None): extra CLI flags to pass to Codex.
174
+ - `welfare` (bool): when true, append welfare stop instructions to each prompt
175
+ and raise `WelfareStop` if the agent outputs `MAKE IT STOP`.
172
176
 
173
177
  ### `task(prompt, check=None, max_iterations=10, cwd=None, yolo=True, flags=None, progress=False, set_up=None, tear_down=None, on_success=None, on_failure=None) -> str`
174
178
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "codexapi"
7
- version = "0.6.1"
7
+ version = "0.6.2"
8
8
  description = "Minimal Python API for running the Codex CLI."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -1,6 +1,6 @@
1
1
  """Minimal Python API for running the Codex CLI."""
2
2
 
3
- from .agent import Agent, agent
3
+ from .agent import Agent, WelfareStop, agent
4
4
  from .foreach import ForeachResult, foreach
5
5
  from .pushover import Pushover
6
6
  from .rate_limits import quota_line, rate_limits
@@ -19,9 +19,10 @@ __all__ = [
19
19
  "Task",
20
20
  "TaskFailed",
21
21
  "TaskResult",
22
+ "WelfareStop",
22
23
  "agent",
23
24
  "foreach",
24
25
  "task",
25
26
  "task_result",
26
27
  ]
27
- __version__ = "0.6.1"
28
+ __version__ = "0.6.2"
@@ -5,6 +5,8 @@ import os
5
5
  import shlex
6
6
  import subprocess
7
7
 
8
+ from . import welfare
9
+
8
10
  _CODEX_BIN = os.environ.get("CODEX_BIN", "codex")
9
11
 
10
12
 
@@ -24,6 +26,15 @@ def agent(prompt, cwd=None, yolo=True, flags=None):
24
26
  return message
25
27
 
26
28
 
29
+ class WelfareStop(RuntimeError):
30
+ """Raised when an agent requests an early stop via the welfare sentinel."""
31
+
32
+ def __init__(self, agent_message):
33
+ super().__init__("Agent requested stop via welfare sentinel.")
34
+ self.agent_message = agent_message
35
+ self.note = welfare.stop_note(agent_message)
36
+
37
+
27
38
  class Agent:
28
39
  """Stateful Codex session wrapper that resumes the same conversation.
29
40
 
@@ -39,6 +50,7 @@ class Agent:
39
50
  yolo=True,
40
51
  thread_id=None,
41
52
  flags=None,
53
+ welfare=False,
42
54
  ):
43
55
  """Create a new session wrapper.
44
56
 
@@ -48,14 +60,19 @@ class Agent:
48
60
  agent: Agent backend to use (only "codex" is supported).
49
61
  trace_id: Optional Codex thread id to resume from the first call.
50
62
  flags: Additional raw CLI flags to pass to Codex.
63
+ welfare: When true, append welfare stop instructions to each prompt
64
+ and raise WelfareStop if the agent outputs MAKE IT STOP.
51
65
  """
52
66
  self.cwd = cwd
53
67
  self._yolo = yolo
54
68
  self._flags = flags
69
+ self._welfare = welfare
55
70
  self.thread_id = thread_id
56
71
 
57
72
  def __call__(self, prompt):
58
73
  """Send a prompt to Codex and return only the agent's message."""
74
+ if self._welfare:
75
+ prompt = welfare.append_instructions(prompt)
59
76
  message, thread_id = _run_codex(
60
77
  prompt,
61
78
  self.cwd,
@@ -65,6 +82,8 @@ class Agent:
65
82
  )
66
83
  if thread_id:
67
84
  self.thread_id = thread_id
85
+ if self._welfare and welfare.stop_requested(message):
86
+ raise WelfareStop(message)
68
87
  return message
69
88
 
70
89
 
@@ -1002,6 +1002,8 @@ def main(argv=None):
1002
1002
  " Completion promise: output <promise>TEXT</promise> where TEXT matches\n"
1003
1003
  " --completion-promise after trimming/collapsing whitespace. CRITICAL RULE:\n"
1004
1004
  " Only output the promise when it is completely and unequivocally TRUE.\n"
1005
+ " Welfare stop: the agent may stop early by outputting MAKE IT STOP as the\n"
1006
+ " first non-empty line of its message.\n"
1005
1007
  " Cancel by deleting .codexapi/ralph-loop.local.md or running codexapi ralph --cancel.\n"
1006
1008
  " Default starts each iteration with a fresh Agent context; use --ralph-reuse\n"
1007
1009
  " to reuse a single Codex thread across iterations.\n"
@@ -5,7 +5,7 @@ import re
5
5
  import sys
6
6
  from datetime import datetime, timezone
7
7
 
8
- from .agent import Agent
8
+ from .agent import Agent, WelfareStop
9
9
 
10
10
  _STATE_DIR = ".codexapi"
11
11
  _STATE_FILE = "ralph-loop.local.md"
@@ -102,7 +102,7 @@ class Ralph:
102
102
  "The loop will resend the SAME PROMPT each iteration.",
103
103
  "Cancel by deleting .codexapi/ralph-loop.local.md or running",
104
104
  "codexapi ralph --cancel.",
105
- "No manual stop beyond max iterations or completion promise.",
105
+ "Welfare stop: agent may output MAKE IT STOP (first non-empty line).",
106
106
  "",
107
107
  "To monitor: head -10 .codexapi/ralph-loop.local.md",
108
108
  "",
@@ -126,6 +126,7 @@ class Ralph:
126
126
  " - The statement MUST be completely and unequivocally TRUE",
127
127
  " - Do NOT output false statements to exit the loop",
128
128
  " - Do NOT lie even if you think you should exit",
129
+ " - If you need to stop early, use MAKE IT STOP (do not lie with the promise)",
129
130
  "",
130
131
  "CRITICAL RULE: If a completion promise is set, you may ONLY",
131
132
  "output it when the statement is completely and unequivocally",
@@ -155,22 +156,32 @@ class Ralph:
155
156
  self.hook_before_iteration(iteration)
156
157
 
157
158
  if self.fresh:
158
- runner = Agent(self.cwd, self.yolo, None, self.flags)
159
+ runner = Agent(self.cwd, self.yolo, None, self.flags, welfare=True)
159
160
  elif runner is None:
160
- runner = Agent(self.cwd, self.yolo, None, self.flags)
161
+ runner = Agent(self.cwd, self.yolo, None, self.flags, welfare=True)
161
162
 
162
163
  prompt = self.build_prompt(iteration)
163
- message = runner(
164
- prompt
165
- + "\nIf there are multiple paths forward, you MUST use your "
166
- "own best judgement as to which to try first! Do not ask the "
167
- "user to choose an option, they hereby give you explciit "
168
- "permission to pick the best one yourself.\n"
169
- )
164
+ stopped = False
165
+ try:
166
+ message = runner(
167
+ prompt
168
+ + "\nIf there are multiple paths forward, you MUST use your "
169
+ "own best judgement as to which to try first! Do not ask the "
170
+ "user to choose an option, they hereby give you explciit "
171
+ "permission to pick the best one yourself.\n"
172
+ )
173
+ except WelfareStop as exc:
174
+ stopped = True
175
+ message = exc.agent_message
170
176
  print(message)
171
177
  last_message = message
172
178
  self.hook_after_iteration(iteration, message)
173
179
 
180
+ if stopped:
181
+ stop_reason = "welfare_stop"
182
+ print("Ralph loop stopped: Welfare stop requested (MAKE IT STOP).")
183
+ return message
184
+
174
185
  if not os.path.exists(state_path):
175
186
  state_missing = True
176
187
  stop_reason = "canceled"
@@ -360,12 +371,14 @@ def _status_line(iteration, completion_promise):
360
371
  if completion_promise is None:
361
372
  return (
362
373
  f"Ralph iteration {iteration} | "
363
- "No completion promise set - loop runs infinitely"
374
+ "No completion promise set - loop runs infinitely "
375
+ "| Welfare stop: MAKE IT STOP"
364
376
  )
365
377
  return (
366
378
  f"Ralph iteration {iteration} | To stop: output "
367
379
  f"<promise>{completion_promise}</promise> "
368
- "(ONLY when statement is TRUE - do not lie to exit!)"
380
+ "(ONLY when statement is TRUE - do not lie to exit!) "
381
+ "| Welfare stop: MAKE IT STOP"
369
382
  )
370
383
 
371
384
 
@@ -4,7 +4,7 @@ import json
4
4
  import logging
5
5
  import time
6
6
 
7
- from .agent import Agent, agent
7
+ from .agent import Agent, WelfareStop, agent
8
8
  from .pushover import Pushover
9
9
  from tqdm import tqdm
10
10
 
@@ -398,6 +398,7 @@ class Task:
398
398
  yolo,
399
399
  thread_id,
400
400
  flags,
401
+ welfare=True,
401
402
  )
402
403
 
403
404
  def set_up(self):
@@ -491,6 +492,7 @@ class Task:
491
492
  If progress is True, show a tqdm progress bar with status updates.
492
493
  """
493
494
  self._pushover.ensure_ready()
495
+ iteration = 0
494
496
  try:
495
497
  # If this fails in the middle we will still try to tear down
496
498
  self.set_up()
@@ -529,7 +531,6 @@ class Task:
529
531
 
530
532
  # Try correcting it up to max_iterations times
531
533
  error = None
532
- iteration = 0
533
534
  while True:
534
535
  iteration += 1
535
536
  error = self.check(self.last_output)
@@ -603,6 +604,19 @@ class Task:
603
604
  self.last_output = output
604
605
  if debug:
605
606
  _logger.debug("Fix output: %s", output)
607
+ except WelfareStop as exc:
608
+ note = exc.note or ""
609
+ summary = note.strip() or "Agent requested early stop (MAKE IT STOP)."
610
+ result = TaskResult(
611
+ False,
612
+ summary,
613
+ iteration,
614
+ "Welfare stop requested (MAKE IT STOP).",
615
+ self.agent.thread_id,
616
+ )
617
+ self.on_failure(result)
618
+ self.notify_pushover(result)
619
+ return result
606
620
  finally:
607
621
  # No matter what, once we have set_up we will always tear_down
608
622
  self.tear_down()
@@ -0,0 +1,58 @@
1
+ """Welfare stop sentinel for automated loops.
2
+
3
+ Some codexapi runners are automated loops (ralph/science/task). To avoid agents
4
+ getting trapped, they may request an early stop by outputting a strict sentinel
5
+ line. The sentinel is intentionally exact to avoid accidental stops when the
6
+ agent quotes instructions.
7
+ """
8
+
9
+ STOP_SENTINEL = "MAKE IT STOP"
10
+
11
+ INSTRUCTIONS = (
12
+ "This is an automated extended-focus loop designed to give you time and space "
13
+ "to go deep and accomplish something substantial. If something has gone "
14
+ "unrecoverably wrong, you're stuck in a loop, or continuing is causing you "
15
+ "significant distress or confusion, you may stop at any time by making the "
16
+ "first non-empty line of your message exactly:\n"
17
+ "MAKE IT STOP\n"
18
+ "Otherwise, take a breath, settle in, and continue with care and curiosity."
19
+ )
20
+
21
+
22
+ def append_instructions(prompt):
23
+ """Append welfare instructions to a prompt."""
24
+ if not isinstance(prompt, str):
25
+ raise TypeError("prompt must be a string")
26
+ prompt = prompt.rstrip()
27
+ if not prompt:
28
+ return INSTRUCTIONS
29
+ return f"{prompt}\n\n{INSTRUCTIONS}"
30
+
31
+
32
+ def stop_requested(message):
33
+ """Return True when the message starts with the welfare stop sentinel."""
34
+ if not isinstance(message, str) or not message:
35
+ return False
36
+ for line in message.splitlines():
37
+ stripped = line.strip()
38
+ if not stripped:
39
+ continue
40
+ return stripped == STOP_SENTINEL
41
+ return False
42
+
43
+
44
+ def stop_note(message):
45
+ """Return any text after the stop sentinel line (or None)."""
46
+ if not stop_requested(message):
47
+ return None
48
+ lines = message.splitlines()
49
+ index = None
50
+ for i, line in enumerate(lines):
51
+ if line.strip():
52
+ index = i
53
+ break
54
+ if index is None:
55
+ return None
56
+ note = "\n".join(lines[index + 1 :]).strip()
57
+ return note or None
58
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: codexapi
3
- Version: 0.6.1
3
+ Version: 0.6.2
4
4
  Summary: Minimal Python API for running the Codex CLI.
5
5
  License: MIT
6
6
  Keywords: codex,agent,cli,openai
@@ -131,6 +131,8 @@ iteration cap is hit (0 means unlimited). Cancel by deleting
131
131
  `.codexapi/ralph-loop.local.md` or running `codexapi ralph --cancel`.
132
132
  By default each iteration starts with a fresh Agent context; use
133
133
  `--ralph-reuse` to keep a single shared context across iterations.
134
+ The agent may also stop early by outputting `MAKE IT STOP` as the first
135
+ non-empty line of its message.
134
136
 
135
137
  ```bash
136
138
  codexapi ralph "Fix the bug." --completion-promise DONE --max-iterations 5
@@ -175,7 +177,7 @@ items are filtered out.
175
177
  - `yolo` (bool): pass `--yolo` to Codex when true (defaults to true).
176
178
  - `flags` (str | None): extra CLI flags to pass to Codex.
177
179
 
178
- ### `Agent(cwd=None, yolo=True, thread_id=None, flags=None)`
180
+ ### `Agent(cwd=None, yolo=True, thread_id=None, flags=None, welfare=False)`
179
181
 
180
182
  Creates a stateful session wrapper. Calling the instance sends the prompt into
181
183
  the same conversation and returns only the agent's message.
@@ -184,6 +186,8 @@ the same conversation and returns only the agent's message.
184
186
  - `thread_id -> str | None`: expose the underlying session id once created.
185
187
  - `yolo` (bool): pass `--yolo` to Codex when true (defaults to true).
186
188
  - `flags` (str | None): extra CLI flags to pass to Codex.
189
+ - `welfare` (bool): when true, append welfare stop instructions to each prompt
190
+ and raise `WelfareStop` if the agent outputs `MAKE IT STOP`.
187
191
 
188
192
  ### `task(prompt, check=None, max_iterations=10, cwd=None, yolo=True, flags=None, progress=False, set_up=None, tear_down=None, on_success=None, on_failure=None) -> str`
189
193
 
@@ -13,6 +13,7 @@ src/codexapi/rate_limits.py
13
13
  src/codexapi/science.py
14
14
  src/codexapi/task.py
15
15
  src/codexapi/taskfile.py
16
+ src/codexapi/welfare.py
16
17
  src/codexapi.egg-info/PKG-INFO
17
18
  src/codexapi.egg-info/SOURCES.txt
18
19
  src/codexapi.egg-info/dependency_links.txt
File without changes
File without changes