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
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ TYPESAFE_API_KEY=

# Live steering uses Codex App Server by default. Use `exec` as a non-steerable fallback.
# FOREMAN_CODEX_BACKEND=app-server
# Optional worker settings, independent of FOREMAN_DECISION_MODEL.
# FOREMAN_CODEX_EXECUTABLE=codex
# FOREMAN_CODEX_MODEL=your-available-worker-model
# FOREMAN_STEERING_ENABLED=true
# FOREMAN_MAX_STEERS_PER_WORKER=1
# FOREMAN_STEERING_GRACE_SECONDS=30
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,8 @@ The most useful environment overrides are:
| `FOREMAN_MAX_ITERATIONS` | `20` | Semantic decision ceiling |
| `FOREMAN_MAX_CONSECUTIVE_ASSESSMENT_FAILURES` | `3` | Tolerated supervisor failures before escalation |
| `FOREMAN_CODEX_BACKEND` | `app-server` | `app-server` for steering or `exec` fallback |
| `FOREMAN_CODEX_EXECUTABLE` | `codex` | Codex CLI executable or absolute path |
| `FOREMAN_CODEX_MODEL` | unset | Explicit Codex worker model; otherwise use native Codex configuration |
| `FOREMAN_WORKER_BACKEND` | `codex` | `codex`, `opencode`, `hermes`, or `deepagents` worker backend |
| `FOREMAN_DEEPAGENTS_EXECUTABLE` | `dcode` | Installed Deep Agents Code executable |
| `FOREMAN_DEEPAGENTS_MODEL` | unset | Native provider/model identifier |
Expand Down
74 changes: 0 additions & 74 deletions docs/decision-model-validation.md

This file was deleted.

5 changes: 0 additions & 5 deletions docs/decision-models.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,3 @@ envelope differs, then compose `DecisionForemanModel` and `DecisionResponsibilit
adapter. Add configuration selection and offline contract tests for successful predicates, refusals,
malformed responses, deadlines, and client ownership. No worker or policy changes are needed to
support a provider with the same predicate semantics.

## Validation evidence

See [adapter validation](decision-model-validation.md) for offline coverage, live Jev results,
and the pending OpenAI live test.
5 changes: 3 additions & 2 deletions docs/evidence.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Foreman currently models these built-in providers:
| `git.untracked` | Bounded, non-sensitive untracked-file excerpts |
| `repository.instructions` | Applicable root instruction file and contents |
| `tests` | Parsed test summaries from worker output |
| `verification` | Independent verifier-worker results |
| `verification` | Independent verifier-worker results and started/completed lifecycle flags |
| `events` | Recent bounded lifecycle events |
| `history` | Previous decision model result, intervention, and factory errors |

Expand Down Expand Up @@ -70,7 +70,8 @@ request can use the hard limits and retain optional evidence within those limits
its entire work and question definitions and only uses hard validation.

When selected evidence exceeds the effective target, Foreman drops oldest history first, then
shortens optional strings while retaining their beginning and end. Optional
shortens bulky worker transcripts and lifecycle output before reducing source, test, or command
evidence. It retains the beginning and end of shortened strings. Optional
fields can be removed as a final step. The request includes an `evidence_budget` record naming
affected fields and explaining that omitted evidence is unknown, not evidence of success or
absence. Compaction changes only the outgoing copy; saved session history remains intact.
Expand Down
7 changes: 7 additions & 0 deletions docs/hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,3 +194,10 @@ Command evidence is likewise assistant-neutral: a Codex, Pi, or Pi Durable bridg
lifecycle events to `foreman hook`. Foreman owns provider invocation and check-specific evidence,
so a CLI verifier does not need a separate SDK integration for every coding assistant. See
[Evidence providers](evidence.md).

## Hooks inside managed workers

`foreman run` owns the supervision of the workers it launches. It marks their process tree with
`FOREMAN_MANAGED_WORKER=1`; native `foreman hook` commands in that tree return `{}` immediately.
This prevents duplicate routing, assessments, and completion guidance from the attached integration.
Ordinary attached sessions continue to use the configured native hooks. See [worker backends](workers.md).
24 changes: 24 additions & 0 deletions docs/releases/0.5.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Foreman 0.5.1

This release adds provider-independent decision models and fixes managed Codex worker supervision.

- Select Jev or OpenAI for responsibility routing and semantic checks through
`FOREMAN_DECISION_PROVIDER` and `FOREMAN_DECISION_MODEL`. The OpenAI adapter uses the native
Decisions API. Install `foreman-core[openai]` to include its SDK.
- Existing Jev configuration and public compatibility facades continue to work. Generic decision
request budgets and provider/model usage metadata support both adapters.
- Keep decision credentials scoped to the supervisor, including credentials loaded from central
configuration. Existing worker credentials and model settings retain their own scope.
- Set `FOREMAN_CODEX_EXECUTABLE` or `FOREMAN_CODEX_MODEL` to explicitly configure Codex coding
and verifier workers. Defaults still use the installed CLI and native model configuration.
Unsupported worker-model errors escalate with guidance; Foreman does not silently pick a fallback.
- Prevent native Foreman hooks from supervising a factory-owned worker a second time. Normalize
completed App Server output so actual test results and agent conclusions remain readable, and
include explicit independent-verification lifecycle evidence. Evidence compaction reduces bulky
transcripts before cutting source or test evidence. Completion thresholds are unchanged.

See [decision-model configuration](../decision-models.md) and
[worker configuration](../workers.md) for setup and troubleshooting.

These notes prepare 0.5.1 for review. Publishing the matching GitHub release after merge triggers
Foreman's existing Trusted Publisher workflow for PyPI.
2 changes: 1 addition & 1 deletion docs/releasing.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Foreman's PyPI distribution is `foreman-core`. It provides the `foreman` import package and
`foreman` command.

Prepared release notes: [Foreman 0.5.0](releases/0.5.0.md).
Prepared release notes: [Foreman 0.5.1](releases/0.5.1.md).

## One-time setup

Expand Down
35 changes: 34 additions & 1 deletion docs/workers.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,38 @@ one capability and three coroutines:
| Hermes | `FOREMAN_WORKER_BACKEND=hermes` | No — stop/retry only |
| Deep Agents Code | `FOREMAN_WORKER_BACKEND=deepagents` | No — stop/retry only |

## Codex executable and model

By default, Foreman runs `codex` from `PATH` and leaves model selection to native Codex
configuration. Set `FOREMAN_CODEX_EXECUTABLE` to select another installed CLI and
`FOREMAN_CODEX_MODEL` to explicitly select the model for both coding and verifier workers.
The same settings are available as `codex_executable` and `codex_model` in `FactoryConfig`.
The App Server transport sends the model in `thread/start`; the exec transport passes `--model`.
`FOREMAN_DECISION_PROVIDER` and `FOREMAN_DECISION_MODEL` configure only the router and checks.

Model availability depends on the CLI version, authentication method, and account access. If Codex
rejects a model as unavailable or unsupported, Foreman escalates immediately with configuration
guidance. It does not choose another model or spend the retry budget repeating that request.
Check the installed CLI version and available models, then start a new run with a supported
configuration. Ordinary worker failures and transient transport errors keep their existing policy.
See the official [Codex model documentation](https://learn.chatgpt.com/docs/models) and
[CLI reference](https://learn.chatgpt.com/docs/cli/reference).

## Supervision ownership and output

Workers launched by `foreman run` receive `FOREMAN_MANAGED_WORKER=1`. A native `foreman hook`
invocation in that process tree returns an empty response before loading configuration or calling
a decision provider. The factory owns supervision for those workers, including the independent
verifier. This prevents a second supervisor from injecting competing completion guidance.
Standalone attached sessions retain their normal hook behavior. Custom worker implementations
should reuse `worker_environment()` so the same ownership marker applies.

The App Server transport preserves streamed output once. If Codex supplies only a completed item,
Foreman records its actual command output and exit code, agent text, or changed paths, rather than
an escaped copy of the full protocol object and patch. Test summaries remain readable by the
pytest parser. Verification lifecycle flags accompany the verifier results in decision evidence;
they do not replace the completion and test checks or change their thresholds.

## Pi and Pi Durable

Pi and Pi Durable use [attached supervision through their native bridges](../integrations/pi/README.md).
Expand Down Expand Up @@ -70,7 +102,8 @@ for model credentials, backend settings, and the separate native-hook path.
subprocess template: bounded streaming, `start_new_session` process
groups, interrupt-then-kill termination).
2. Keep the agent's environment clean: reuse `worker_environment()`, which
inherits the process environment minus Foreman's `TYPESAFE_*` credentials.
inherits the process environment minus Foreman's `TYPESAFE_*` and `FOREMAN_OPENAI_*`
credentials and marks factory-owned children to prevent duplicate hook supervision.
3. Wire it into `FactoryRuntime`'s default worker factory (or pass your own
`worker_factory`) and set `supports_steering` accurately. Unknown custom
workers default to non-steerable for backward compatibility.
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "foreman-core"
version = "0.5.0"
version = "0.5.1"
description = "An agent supervisor and software factory foreman powered by decision models"
readme = "README.md"
requires-python = ">=3.11"
Expand Down
5 changes: 5 additions & 0 deletions src/foreman/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,11 @@ def hook(
) -> None:
"""Process one coding-assistant hook event from stdin."""

if os.getenv("FOREMAN_MANAGED_WORKER") == "1":
# The owning factory already supplies routing, checks, and completion control.
sys.stdout.write("{}\n")
return

extensions: ExtensionManager | None = None
runtime_started = False
try:
Expand Down
4 changes: 4 additions & 0 deletions src/foreman/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ class FactoryConfig(BaseModel):
hermes_max_turns: int = Field(default=200, ge=1)
hermes_toolsets: str | None = None
max_consecutive_assessment_failures: int = Field(default=3, ge=1)
codex_executable: str = Field(default="codex", min_length=1)
codex_model: str | None = Field(default=None, min_length=1)
codex_backend: Literal["app-server", "exec"] = "app-server"
steering_enabled: bool = True
max_steers_per_worker: int = Field(default=1, ge=0)
Expand Down Expand Up @@ -177,6 +179,8 @@ def from_environment(cls) -> FactoryConfig:
"max_consecutive_assessment_failures",
int,
),
"FOREMAN_CODEX_EXECUTABLE": ("codex_executable", str),
"FOREMAN_CODEX_MODEL": ("codex_model", str),
"FOREMAN_CODEX_BACKEND": ("codex_backend", str),
"FOREMAN_STEERING_ENABLED": (
"steering_enabled",
Expand Down
2 changes: 1 addition & 1 deletion src/foreman/evidence.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class EvidenceSelection(Protocol):
"git.untracked": ("untracked_evidence",),
"repository.instructions": ("agents_md_path", "agents_md_instructions"),
"tests": ("test_results",),
"verification": ("verification_results",),
"verification": ("verification_started", "verification_completed", "verification_results"),
"events": ("recent_events",),
"history": ("previous_result", "previous_intervention", "failures"),
}
Expand Down
1 change: 1 addition & 0 deletions src/foreman/models/worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class WorkerRecord(BaseModel):
stdout: str = ""
stderr: str = ""
termination_reason: str | None = None
configuration_error: str | None = Field(default=None, max_length=4000)
client: str | None = None
client_session_id: str | None = None
client_turn_id: str | None = None
Expand Down
5 changes: 5 additions & 0 deletions src/foreman/observation.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ class FactoryObservation(BaseModel):
agents_md_path: str | None = None
agents_md_instructions: str = ""
test_results: list[dict[str, Any]]
verification_started: bool = False
verification_completed: bool = False
verification_results: list[dict[str, Any]]
command_evidence: list[dict[str, Any]] = Field(default_factory=list)
recent_events: list[dict[str, Any]]
Expand Down Expand Up @@ -146,6 +148,7 @@ def _bounded_worker(worker: WorkerRecord, output_limit: int) -> dict[str, Any]:
"stdout_tail": _tail(worker.stdout, output_limit),
"stderr_tail": _tail(worker.stderr, output_limit),
"termination_reason": worker.termination_reason,
"configuration_error": worker.configuration_error,
"client": worker.client,
"client_session_id": worker.client_session_id,
"client_turn_id": worker.client_turn_id,
Expand Down Expand Up @@ -402,6 +405,8 @@ async def build_observation(
f"{latest.stdout}\n{latest.stderr}" if latest else ""
)
],
verification_started=state.verification_started,
verification_completed=state.verification_completed,
verification_results=[
result.model_dump(mode="json") for result in state.verification_results
],
Expand Down
12 changes: 12 additions & 0 deletions src/foreman/policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,22 @@ def _runtime_directive(
priority=priority,
)

def configuration_error(self, state: FactoryState) -> Directive | None:
for worker in state.workers:
if worker.configuration_error:
return self._runtime_directive(
state, InterventionType.ESCALATE, worker.configuration_error, priority=1000,
)
return None

def evaluate(self, state: FactoryState, result: ForemanResult) -> ForemanResult:
assert self.responsibilities is not None
proposed = self.responsibilities.directives(state, result)

configuration_error = self.configuration_error(state)
if configuration_error is not None:
proposed.append(configuration_error)

# Hard iteration limits remain a runtime invariant, not responsibility semantics.
if self.enforce_iteration_limit and state.iteration >= state.max_iterations:
proposed.append(
Expand Down
Loading
Loading