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
34 changes: 34 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,40 @@ versioning when releases are cut.
and keep scheduled public runs inert so public publishing stays downstream of
the internal source-of-truth release.

## [0.10.89] - 2026-09-14

### Changed

- Use GPT-5.6 as the default for OpenAI and Codex provider routes; preserve explicitly configured models.
- Refresh bundled model context and output limits from the catalog source (#9120).
- Require a signed handoff before a public release can be published (#9101). <!-- maestro-release-note:f558cf8e65ea -->
- Keep build caches complete so builds stay fast (#9093). <!-- maestro-release-note:2d2587bdcd26 -->
- Verify changes that land through the merge queue with the same required checks as direct merges (#9098). <!-- maestro-release-note:725395442e85 -->
- Start runtime verification faster so pre-release checks finish reliably (#9097). <!-- maestro-release-note:6f739e90af6d -->
- Verify every change on the main branch individually so release gates never wait on a skipped run (#9091). <!-- maestro-release-note:03d9a9925c7d -->
- Treat the hosted-orb smoke test as a hard release gate and unblock supply-chain policy reviews (#9086). <!-- maestro-release-note:f43ad5722476 -->
- Promote runtime images only after the Maestro image is published so deployments never reference a missing image (#9087). <!-- maestro-release-note:24c7ddd1d927 -->
- Restore reliable verification of every change on the main branch (#9085). <!-- maestro-release-note:f3e81b3857b6 -->
- Publish releases from GitHub, including native artifact validation and a stable Linux release ABI (#9079). <!-- maestro-release-note:847da3e0e2d6 -->
- Move Maestro's continuous integration to GitHub Actions (#9080). <!-- maestro-release-note:47d7b7c390a1 -->
- Remove files left over from the retired CI system (#9081). <!-- maestro-release-note:48e69219206f -->
- Remove tooling tied to the retired CI system from the public port (#9078). <!-- maestro-release-note:812cb7ab578e -->

### Fixed

- Keep pre-merge checks within build-machine capacity so they no longer time out (#9116). <!-- maestro-release-note:94aee070052e -->
- Keep Rust builds within build-machine memory and CPU limits so they no longer fail mid-build (#9113). <!-- maestro-release-note:e0bfe7ce765a -->
- Verify cerebro row-level security under non-superuser database roles so tenant isolation is tested for real (#9110). <!-- maestro-release-note:e0da8e11e8f9 -->
- Keep the public mirror syncing when review bots leave open threads on generated sync PRs (#9107). <!-- maestro-release-note:17b0068a392b -->
- Fix mobile acceptance tests failing on a missing protobuf runtime (#9105). <!-- maestro-release-note:e772e4f07b84 -->
- Fix the APEX readiness check timing out before it could report (#9103). <!-- maestro-release-note:dded86283045 -->
- Recover publication retries and refresh app progress (#9090). <!-- maestro-release-note:df4ae346f1ca -->
- Speed up affected-component checks by avoiding repeated file lookups (#9096). <!-- maestro-release-note:a0191e6cda16 -->
- Complete the Sandboxwich CI migration (#9095). <!-- maestro-release-note:bc759b64456c -->
- Fix mobile conversation submissions that the platform rejected with HTTP 415 (#9099). <!-- maestro-release-note:13bd3be6a765 -->
- Retain organization scope in attention events (#9094). <!-- maestro-release-note:4de9281c7728 -->
- Include the active workflow inputs in queue reports so stale reports are distinguishable (#9088). <!-- maestro-release-note:2aa0a8cb4368 -->

## [0.10.88] - 2026-09-13

### Added
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ ignore = [

# --- Unsound (no known exploitable vulnerability reported here) ---
{ id = "RUSTSEC-2026-0002", reason = "lru 0.12.5: IterMut violates Stacked Borrows (Miri/unsafe-code soundness issue), pulled in by ratatui (production). Not a memory-safety issue under normal (non-Miri) execution; tracked for the next ratatui bump that picks up a fixed lru. expires: 2026-10-23" },
{ id = "RUSTSEC-2026-0285", reason = "rustls <0.23.45 accepts TLS 1.3 handshake messages at the wrong encryption level after a key change in the same record (GHSA-2mjx-qc3c-rqvc, CVSS 5.3). Fixed in rustls 0.23.45, published 2026-09-14; that upgrade also resolves aws-lc-rs 1.18.1 and aws-lc-sys 0.45.0, all still inside the 14-day dependency-admission cool-off in security/rust-dependency-admission.toml. Bump the lockfile once they age past it. expires: 2026-09-30" },
Comment thread
evalops-mirror[bot] marked this conversation as resolved.
Comment thread
evalops-mirror[bot] marked this conversation as resolved.
]

[licenses]
Expand Down
86 changes: 54 additions & 32 deletions docs/protocols/hosted-runner-contract.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Hosted Runner Contract

> **Status:** This document predates the Rust-only runtime migration (#3016, #3017, merged 2026-07-22), which deleted Maestro's TypeScript agent runtime and SDK. Hosted runner code now lives in `packages/local-host-rs/src/hosted_runner.rs`, `hosted_runner_cli.rs`, and `packages/tui-rs/src/hosted_runner/`. Some file paths below may be stale; they are kept for design context and updated only where a corresponding Rust module was confirmed.
> **Status:** This document predates the Rust-only runtime migration (#3016, #3017, merged 2026-07-22), which deleted Maestro's TypeScript agent runtime and SDK. Hosted runner code now lives in `packages/local-host-rs/src/hosted_runner/` (config, handle, manifests, snapshots, rendezvous, workload identity) and `packages/local-host-rs/src/hosted_runner_cli.rs` (launch resolution and the `hosted-runner` entrypoint). The startup coordinates in [Configuration](#configuration) are executable: `packages/local-host-rs/tests/hosted_runner_startup_contract.rs` runs every case in `packages/local-host-rs/tests/fixtures/hosted-runner-startup-contract.json` through the real CLI resolver, so a documented coordinate that stops being honored fails `cargo test -p maestro-local-host`. Other file paths below may be stale; they are kept for design context and updated only where a corresponding Rust module was confirmed.


Maestro hosted runners are substrate-neutral runtime pods or sandboxes that
Expand Down Expand Up @@ -38,17 +38,34 @@ flags or environment variables, but the resolved values are the contract.

| Contract field | Flags and environment | Required |
| --- | --- | --- |
| Runner session id | `--runner-session-id`, `MAESTRO_RUNNER_SESSION_ID`, `REMOTE_RUNNER_SESSION_ID` | yes |
| Workspace root | `--workspace-root`, `MAESTRO_WORKSPACE_ROOT`, `WORKSPACE_ROOT` | yes |
| Listen address | `--listen`, `--host`, `--port`, `MAESTRO_HOSTED_RUNNER_LISTEN`, `MAESTRO_HOSTED_RUNNER_HOST`, `MAESTRO_HOSTED_RUNNER_PORT`, `PORT` | yes |
| Owner generation | `--owner-instance-id`, `MAESTRO_REMOTE_RUNNER_OWNER_INSTANCE_ID`, `REMOTE_RUNNER_OWNER_INSTANCE_ID` | required when Platform fences owners |
| Runtime generation | `MAESTRO_PLACEMENT_GENERATION`, `MAESTRO_SANDBOXWICH_PLACEMENT_GENERATION`, `MAESTRO_REMOTE_RUNNER_GENERATION` | required for managed durable threads; the canonical placement variable takes precedence |
| Snapshot root | `--snapshot-root`, `MAESTRO_REMOTE_RUNNER_SNAPSHOT_ROOT`, `REMOTE_RUNNER_SNAPSHOT_ROOT` | optional |
| Restore manifest | `MAESTRO_REMOTE_RUNNER_RESTORE_MANIFEST`, `REMOTE_RUNNER_RESTORE_MANIFEST` | optional |
| Runner session id | `--runner-session-id`, `MAESTRO_RUNNER_SESSION_ID` (deprecated `REMOTE_RUNNER_SESSION_ID`) | yes |
| Workspace root | `--workspace-root`, `MAESTRO_WORKSPACE_ROOT` (deprecated `WORKSPACE_ROOT`) | yes |
| Listen address | `--listen`, `--host`, `--port`, `MAESTRO_HOSTED_RUNNER_LISTEN`, `MAESTRO_HOSTED_RUNNER_HOST`, `MAESTRO_HOSTED_RUNNER_PORT` (deprecated `PORT`) | yes |
| Owner generation | `--owner-instance-id`, `MAESTRO_REMOTE_RUNNER_OWNER_INSTANCE_ID` (deprecated `REMOTE_RUNNER_OWNER_INSTANCE_ID`) | required when Platform fences owners |
| Runtime generation | `MAESTRO_PLACEMENT_GENERATION`, `MAESTRO_SANDBOXWICH_PLACEMENT_GENERATION`, `MAESTRO_REMOTE_RUNNER_GENERATION` | required for managed durable threads; the canonical placement variable takes precedence and also enters workload-identity mode, so it must arrive with the full identity set below |
| Snapshot root | `--snapshot-root`, `MAESTRO_REMOTE_RUNNER_SNAPSHOT_ROOT` (deprecated `REMOTE_RUNNER_SNAPSHOT_ROOT`) | optional |
| Restore manifest | `MAESTRO_REMOTE_RUNNER_RESTORE_MANIFEST` (deprecated `REMOTE_RUNNER_RESTORE_MANIFEST`) | optional |
| Workspace id | `--workspace-id`, `MAESTRO_REMOTE_RUNNER_WORKSPACE_ID`, `MAESTRO_WORKSPACE_ID` | optional |
| Agent run id | `--agent-run-id`, `MAESTRO_AGENT_RUN_ID` | optional |
| Existing Maestro session | `--maestro-session-id`, `MAESTRO_SESSION_ID` | optional |
| Attach audience | `--attach-audience`, `MAESTRO_ATTACH_AUDIENCE` | optional |
| Attach bearer token | `MAESTRO_HOSTED_RUNNER_AUTH_TOKEN` or `MAESTRO_HOSTED_RUNNER_AUTH_TOKEN_FILE` (deprecated `MAESTRO_WEB_API_KEY`, `MAESTRO_WEB_API_KEY_FILE`) | required unless workload identity is present or `MAESTRO_WEB_REQUIRE_KEY=0` on a loopback bind; inline and file forms are mutually exclusive |
| Headless model binding | `MAESTRO_MODEL`; managed `evalops/` and `maestro-managed/` models also require `MAESTRO_RESIDENT_CONTRACT_REVISION` pinned to the current resident revision plus non-empty `MAESTRO_EVALOPS_ACCESS_TOKEN`, `MAESTRO_EVALOPS_BASE_URL`, `MAESTRO_EVALOPS_ORG_ID`, `MAESTRO_EVALOPS_WORKSPACE_ID`, and `MAESTRO_EVALOPS_PROVIDER` | yes |
| Headless child | `--agent-cli-path`, `MAESTRO_HEADLESS_CLI_PATH` | optional |
| Launch spec | `--config` or `MAESTRO_HOSTED_LAUNCH_SPEC_FILE` (not both); when present it replaces the flag/environment coordinates above | optional |
| Rendezvous | `MAESTRO_RENDEZVOUS_MODE` (`inbound`, `outbound_shadow`, `outbound`); outbound modes require workload identity | optional |

Deprecated environment keys are the entries of `DEPRECATED_ENV_ALIASES` in
`packages/local-host-rs/src/hosted_runner/config.rs`. The canonical key always
wins when both are set; every deprecated key that carries a value produces one
`hosted-runner environment key ... is deprecated` startup warning, and the
deprecated keys stop being read after the next Maestro minor release. Platform
(`runner-host` and `sandboxwich`) already launches with the canonical keys.

Startup fails closed on a missing or blank runner session id, a missing or
non-directory workspace root, a non-integer runtime generation, a non-loopback
bind without attach authentication, and any partially supplied workload
identity set.

Managed Kubernetes workload-identity mode additionally requires all of the
following values. Supplying only part of the set fails startup; static hosted
Expand All @@ -66,7 +83,8 @@ runner bearer authentication is forbidden when the set is present.
| Runner session binding | `MAESTRO_RUNNER_SESSION_ID` |

There is intentionally no file or environment input for a Runner Host client
CA, Maestro server certificate, or private key. The authenticated exchange
CA, Maestro server certificate, or private key; setting
`MAESTRO_RUNNER_CLIENT_CA_FILE` rejects startup. The authenticated exchange
response carries the workload CA in memory. Maestro uses that CA only for the
current server chain and Runner Host client verification, pinned to the exact
client URI `spiffe://identity.evalops.dev/service/runner-host`.
Expand Down Expand Up @@ -464,29 +482,33 @@ the operational model.

## Conformance

Every hosted runner implementation should satisfy the shared conformance suite:

```bash
npm run test -- test/headless/runtime-conformance.test.ts
```

Rust hosted-runner wire parity is enforced by the dedicated
`rust-hosted-conformance` CI job. Run the same gate locally with:

```bash
MAESTRO_RUST_HOSTED_CONFORMANCE=1 npm run test -- test/headless/runtime-conformance.test.ts
```

Current coverage includes schema-valid snapshots/envelopes, controller/viewer
roles, explicit controller takeover, cursor replay/reset, approval request and
response resolution, workspace-root file-read enforcement, utility
command/search/watch lifecycle, and disconnect cleanup.

The TypeScript adapter targets the in-process host. The Rust-hosted adapter
drives the same scenarios through
`maestro_tui::hosted_runner::start_hosted_runner_with_message_executor` and the
external HTTP/SSE surface. The scenario body must remain shared; only adapter
startup and transport details should vary.
Two executable suites back this contract; both run in the Maestro CI Rust lane.

Startup and lifecycle (`cargo test -p maestro-local-host --test hosted_runner_startup_contract`):

- `packages/local-host-rs/tests/fixtures/hosted-runner-startup-contract.json`
is the accept/reject matrix for the [Configuration](#configuration)
coordinates. Each case is resolved through
`hosted_runner_cli::resolve_hosted_runner_launch_config`, the same code path
as `maestro hosted-runner`, and asserts either the resolved contract fields or
the fail-closed error. Adding a coordinate means adding a fixture case; the
suite rejects any exercised coordinate that this document does not name.
The guard is one-directional: coordinates named here but absent from the
fixture (for example `MAESTRO_REMOTE_RUNNER_SNAPSHOT_ROOT`,
`MAESTRO_REMOTE_RUNNER_RESTORE_MANIFEST`, and the split `--host`/`--port`
flags) are documented, not mechanically enforced.
- The runtime scenario starts the full CLI runtime behind a stub headless child
and checks the identity endpoint (`ready`, `draining`, sparse fields), a
drain that writes the snapshot manifest under the workspace root, and the
post-drain identity transition.

Wire surface (`maestro conformance` and the `rust-hosted-conformance` CI job):
schema-valid snapshots/envelopes, controller/viewer roles, explicit controller
takeover, cursor replay/reset, approval request and response resolution,
workspace-root file-read enforcement, utility command/search/watch lifecycle,
and disconnect cleanup, driven through
`maestro_local_host::hosted_runner_conformance` against the external HTTP/SSE
surface. See [Headless runtime conformance](./headless-conformance.md).

## References

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@evalops/deixic-code",
"description": "Deixic Code — native Rust coding agent, CLI, TUI, and web runtime gateway",
"version": "0.10.88",
"version": "0.10.89",
"private": false,
"type": "module",
"bin": {
Expand Down
2 changes: 1 addition & 1 deletion packages/ai-rs/src/anthropic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ impl AnthropicClient {
/// }
/// ]
/// ```
fn build_request_body(
pub(crate) fn build_request_body(
&self,
messages: &[Message],
config: &RequestConfig,
Expand Down
10 changes: 10 additions & 0 deletions packages/ai-rs/src/google.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,16 @@ impl GoogleClient {
}

/// Build the Gemini API request body
/// Serialized request body, used by the provider conformance matrix.
#[cfg(test)]
pub(crate) fn request_body_json(
&self,
messages: &[Message],
config: &RequestConfig,
) -> Result<serde_json::Value> {
Ok(serde_json::to_value(self.build_request(messages, config)?)?)
}

fn build_request(&self, messages: &[Message], config: &RequestConfig) -> Result<GoogleRequest> {
let messages = crate::cache_topology::messages_with_volatile_tail(messages, config);
let messages = super::transform::google_messages_for_wire(&messages);
Expand Down
47 changes: 40 additions & 7 deletions packages/ai-rs/src/kimi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ impl KimiK3Client {
})
}

fn build_request_body(&self, messages: &[Message], config: &RequestConfig) -> Value {
pub(crate) fn build_request_body(&self, messages: &[Message], config: &RequestConfig) -> Value {
let mut converted = convert_messages(messages);
if let Some(system) = config
.system
Expand Down Expand Up @@ -299,7 +299,7 @@ fn tool_definition(tool: &Tool) -> Value {
fn convert_messages(messages: &[Message]) -> Vec<Value> {
let mut converted = Vec::new();

for message in messages {
for message in &super::transform::repair_tool_sequence(messages.to_vec()) {
if let MessageContent::Blocks(blocks) = &message.content {
let tool_results = blocks.iter().filter_map(|block| match block {
ContentBlock::ToolResult {
Expand All @@ -309,18 +309,13 @@ fn convert_messages(messages: &[Message]) -> Vec<Value> {
} => Some((tool_use_id, content)),
_ => None,
});
let mut emitted_tool_result = false;
for (tool_use_id, content) in tool_results {
emitted_tool_result = true;
converted.push(json!({
"role": "tool",
"tool_call_id": tool_use_id,
"content": content,
}));
}
if emitted_tool_result {
continue;
}
}

match &message.content {
Expand Down Expand Up @@ -636,6 +631,44 @@ mod tests {
assert_eq!(body["model"], "kimi-k3");
}

#[test]
fn unmatched_results_in_a_mixed_batch_reach_the_model_as_text() {
let messages = vec![
Message {
role: Role::Assistant,
content: MessageContent::Blocks(vec![ContentBlock::ToolUse {
id: "call_a".to_string(),
name: "read".to_string(),
input: json!({}),
gemini_context: None,
}]),
},
Message {
role: Role::User,
content: MessageContent::Blocks(vec![
ContentBlock::ToolResult {
tool_use_id: "call_a".to_string(),
content: "matched".to_string(),
is_error: None,
},
ContentBlock::ToolResult {
tool_use_id: "call_stale".to_string(),
content: "retained evidence".to_string(),
is_error: None,
},
]),
},
];

let converted = convert_messages(&messages);
assert_eq!(converted[1]["role"], "tool");
assert_eq!(converted[1]["tool_call_id"], "call_a");
assert_eq!(converted[2]["role"], "user");
let text = converted[2]["content"].as_str().unwrap();
assert!(text.contains("retained evidence"), "{text}");
assert_eq!(converted.len(), 3);
}

#[test]
fn thinking_budget_maps_to_kimi_supported_effort_levels() {
let client = KimiK3Client::new("test-key", "https://api.moonshot.ai/v1").unwrap();
Expand Down
2 changes: 2 additions & 0 deletions packages/ai-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ pub mod op_secret;
mod openai;
#[path = "openai.rs"]
mod openai_base;
#[cfg(test)]
mod provider_matrix;
mod providers;
pub mod sanitize;
mod scripted;
Expand Down
2 changes: 1 addition & 1 deletion packages/ai-rs/src/openai.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2022,7 +2022,7 @@ impl OpenAiClient {
)
}

fn build_request_body_for_api(
pub(crate) fn build_request_body_for_api(
&self,
messages: &[Message],
config: &RequestConfig,
Expand Down
Loading
Loading