Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
1be4905
Add declared native delivery selector
myobie Aug 10, 2026
8f8ec5f
Add controlled Codex app-server binding
myobie Aug 10, 2026
4eeb163
Track controlled Codex turn state
myobie Aug 10, 2026
2614c84
Disambiguate Codex response IDs
myobie Aug 10, 2026
69c5b72
Deliver Codex DINGs through app server
myobie Aug 10, 2026
ede2926
Keep Codex delivery held through review completion
myobie Aug 10, 2026
c05a80b
Keep duplicate Codex hold events typed
myobie Aug 10, 2026
8979978
Trust typed Codex review holds
myobie Aug 10, 2026
538de2f
Keep Codex transport test payload-neutral
myobie Aug 11, 2026
3e99668
Persist typed Codex delivery receipts
myobie Aug 10, 2026
e5adaee
Subscribe new idle Codex threads
myobie Aug 10, 2026
d83fecf
Activate fresh Codex thread bindings
myobie Aug 10, 2026
7476823
Accept verified Codex 0.146 protocol
myobie Aug 11, 2026
c461f21
Explain Codex version admission gate
myobie Aug 11, 2026
a18f115
Forward Codex config to app server
myobie Aug 11, 2026
de63080
Persist Codex wrapper startup diagnostics
myobie Aug 11, 2026
55bdb8d
Reap children when diagnostics fail
myobie Aug 11, 2026
85d8676
Bind Codex resume from control response
myobie Aug 11, 2026
06198af
Wait for Codex TUI before control resume
myobie Aug 11, 2026
8ec67bc
Trace TUI-loaded resume gate
myobie Aug 11, 2026
381b07e
Separate Codex resume diagnostics deadline
myobie Aug 11, 2026
0246032
Project hook trust for remote resume
myobie Aug 12, 2026
4aa4a3d
Constrain transient hook trust authorization
myobie Aug 12, 2026
5ec6e02
Add Claude-owned MCP inbox watcher
myobie Aug 12, 2026
2c1de67
Clarify Codex native ownership boundary
myobie Aug 12, 2026
4572156
Refresh Codex native presence from session loop
myobie Aug 12, 2026
7b22c3b
Retry transient Codex handshake reads
myobie Aug 13, 2026
b8c61db
Give Darwin Codex fixtures scheduler headroom
myobie Aug 13, 2026
331585d
Report unexpected Codex control event ordering
myobie Aug 13, 2026
ff343fb
Avoid sub-millisecond Darwin socket timeout
myobie Aug 13, 2026
7d8635a
Add Codex control pump operation diagnostics
myobie Aug 13, 2026
d5a4adc
Treat Darwin closed-socket timeout as control closure
myobie Aug 13, 2026
60e0d8c
Drain buffered Codex frames after Darwin close
myobie Aug 13, 2026
89bba7d
Instrument Codex TUI gate event flow
myobie Aug 13, 2026
564bff6
Give Codex TUI gate fixture Darwin headroom
myobie Aug 13, 2026
44fad3b
Expose native Claude and Codex driver commands
myobie Aug 13, 2026
ea43806
Add typed harness driver declarations
myobie Aug 14, 2026
23511ef
Expand harness drivers to plain KDL
myobie Aug 14, 2026
4da52d5
Snapshot harness driver expansion
myobie Aug 14, 2026
8f6c416
Print typed harness driver expansion
myobie Aug 14, 2026
77be024
Reject conflicting driver launch sources
myobie Aug 14, 2026
9a677a2
Compile driver blocks into agent tasks
myobie Aug 14, 2026
138a949
Materialize shared driver render expansion
myobie Aug 14, 2026
e46a702
Recognize driver delivery in doctor
myobie Aug 14, 2026
dd9d858
Preserve driver runtime safety gates
myobie Aug 14, 2026
6954bcb
Resolve driver wrappers to current binary
myobie Aug 14, 2026
ba3f9b9
Keep driver compilation clippy clean
myobie Aug 14, 2026
277b35e
Compile driver tasks for inventory
myobie Aug 14, 2026
b21f6d7
Compile driver launches before inspection
myobie Aug 14, 2026
ceeb5b8
Reap app-server process groups
myobie Aug 14, 2026
67be911
Prove idle native presence refresh
myobie Aug 14, 2026
5e17b08
Keep replicated presence alive
myobie Aug 14, 2026
3687a0b
Conform presence records to version 1
myobie Aug 14, 2026
0fabb77
Name the session owner in presence spec
myobie Aug 16, 2026
cbfaa0f
Rename the Claude MCP driver
myobie Aug 16, 2026
df9c73b
Warn on the deprecated Claude driver name
myobie Aug 16, 2026
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
166 changes: 162 additions & 4 deletions 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 Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ sha2 = "0.10"
st2-wire = { path = "crates/st2-wire" }
tempfile = "3"
toml = "0.9"
tungstenite = "0.30"

[dev-dependencies]
libc = "0.2"
Expand Down
4 changes: 2 additions & 2 deletions INVARIANTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ materialization, messaging, DING, or presence must preserve them.
| **Mutation-only filesystem wakeups** | Supervisor and DING filesystem watchers ignore read/open access events and wake early only for create, modify, rename, or remove events. Their own catalog and inbox reads therefore cannot bypass the bounded timer cadence or form a Linux inotify CPU loop. | `src/watch.rs::only_mutations_wake_watch_loops`; `src/watch.rs::linux_reads_are_silent_but_real_mutations_wake`; `src/ding/mod.rs::idle_ding_does_not_spin_on_its_own_inbox_reads`; `src/run.rs::idle_supervisor_does_not_spin_on_its_own_catalog_reads` |
| **Bounded DING PTY probe churn** | An unsafe or active composer retains its FIFO notice but deferred delivery retries use a bounded backoff, so each inbox poll cannot spawn another short-lived PTY probe. | `src/ding/mod.rs::deferred_delivery_backoff_bounds_short_lived_pty_attempts` |
| **Agent-declared presence discipline** | The shipped bus contract requires agents to declare `busy` before executing work, use `available` only while yielding or ready, and reserve `dnd` for an explicit hold. Both native harnesses materialize that contract. Busy remains observable but does not suppress DING; fresh `dnd` is the only delivery gate. | `tests/native_only.rs::clean_path_executes_the_maintained_native_authoring_guide`; `src/ding/mod.rs::pending_delivery_ignores_busy_but_respects_fresh_dnd_archive_and_retry` |
| **Stable roster JSON** | `st2 agents --json [--enrich]` preserves field names, order, null handling, presence, typed desired state and rationale, the retirement compatibility projection, opaque declared Resource descriptors, activity, and inbox counts. Presence remains independent from desired lifecycle. | `src/agents.rs::agents_json_has_stable_wire_shape`; `src/agents.rs::agents_json_preserves_opaque_declared_resource_descriptors`; `tests/status_agents.rs::roster_json_and_human_output_distinguish_retirement_from_presence`; `tests/status_agents.rs::roster_keeps_presence_separate_from_suspended_desired_state` |
| **Agent-declared presence** | Refresh preserves non-DND declared status and only advances liveness; a missing status starts as `available`, while `dnd` is never refreshed and an unrefreshed declaration ages to `unknown`. | `src/status.rs::refresh_preserves_value_and_bumps_mtime`; `src/status.rs::refresh_leaves_dnd_to_age_out`; `src/status.rs::refresh_missing_writes_available_default`; `src/status.rs::stale_mtime_reads_as_unknown_regardless_of_contents` |
| **Stable roster JSON** | `st2 agents --json [--enrich]` preserves field names, order, null handling, presence, typed desired state and rationale, the retirement compatibility projection, opaque declared Resource descriptors, origin-timed activity, and inbox counts. Presence remains independent from desired lifecycle. | `src/agents.rs::agents_json_has_stable_wire_shape`; `src/agents.rs::agents_json_preserves_opaque_declared_resource_descriptors`; `tests/status_agents.rs::roster_json_and_human_output_distinguish_retirement_from_presence`; `tests/status_agents.rs::roster_keeps_presence_separate_from_suspended_desired_state`; `tests/status_agents.rs::roster_uses_version_1_origin_time_for_last_activity` |
| **Agent-declared presence** | Refresh preserves non-DND declared status and advances the version 1 heartbeat. A missing status starts as `available`. Legacy DND migrates without renewing its hold. Version 1 DND is not refreshed. Stale, malformed, or implausibly future heartbeats read as `unknown`. The outer Codex and Claude session wrappers own a five-minute heartbeat while their provider remains alive. | `src/status.rs::refresh_preserves_value_and_changes_heartbeat_bytes`; `src/status.rs::refresh_upgrades_legacy_dnd_without_renewing_the_hold`; `src/status.rs::refresh_missing_writes_available_default`; `src/status.rs::version_1_staleness_and_future_skew_are_bounded`; `src/status.rs::malformed_versioned_record_is_unknown_without_mtime_fallback`; `src/claude_session.rs::idle_provider_refreshes_presence_without_mcp_input`; `src/codex_app_server.rs::inbox_fallback_does_not_write_a_fifteen_second_presence_heartbeat` |
| **Retirement health** | A retired declaration is healthy only after every declared task ID is absent. Any live or dead declared task record reports incomplete retirement; retired declarations do not require presence. Live declarations retain their existing task and presence checks. | `tests/doctor.rs::retired_declaration_is_healthy_when_tasks_and_presence_are_absent`; `tests/doctor.rs::retired_declaration_is_unhealthy_while_a_declared_task_is_alive`; `tests/doctor.rs::retired_declaration_is_unhealthy_while_a_dead_task_record_remains` |
| **Suspension health** | A suspended declaration is healthy when no declared task is live and every retained dead record is explicitly keep-pinned. It requires no presence, but this weaker result never proves retirement. Resume preserves ordinary keep and adopt-only policy. | `tests/doctor.rs::suspended_declaration_is_healthy_when_tasks_are_absent_without_presence`; `tests/doctor.rs::suspended_declaration_distinguishes_live_dead_keep_and_dead_nonkeep`; `tests/reconcile.rs::resuming_uses_ordinary_reconcile_and_does_not_override_keep` |
| **Crash loops surface** | A task parked by a fail-mode restart policy notifies its supervisor once over the bus. | `tests/run.rs::surface_crash_loop_notifies_the_supervisor_over_the_bus` |
Expand Down
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -475,9 +475,15 @@ contract; renderer changes can defer delivery and remain an explicit design gap.

Agents must declare `busy` before actively executing work and return to `available` only when
yielding or ready for new work, but `busy` never suppresses DING. Fresh `dnd` is the only delivery
hold. The sidecar does not refresh `dnd`, so an abandoned hold becomes stale after 15 minutes and
delivery resumes. New arrivals remain FIFO, same-filename archive receipts shadow and clean restored
inbox duplicates, and failed or uncertain PTY operations retain the notice for safe retry. Unsafe
hold. Each status record keeps the state on its first line. Version 1 writers add `v1 <unix-ms>` on
the second line. New readers use that origin timestamp and accept a legacy bare state. Old readers
keep using the first line and file mtime. A live session owner refreshes non-DND presence every five
minutes. This changes the replicated bytes and stays below the 15-minute stale limit. A legacy
`dnd` upgrades once with its existing mtime, then remains unchanged. A malformed versioned record or
a timestamp more than 60 seconds in the future reads as `unknown` without an mtime fallback. Version
1 status contributes its origin timestamp to `lastActivity`. New arrivals remain FIFO.
Same-filename archive receipts shadow and clean
restored inbox duplicates. Failed or uncertain PTY operations retain the notice for safe retry. Unsafe
delivery retries use a bounded backoff, so an active composer cannot make the sidecar spawn a fresh
PTY probe on every inbox poll. On start
or restart, the sidecar first adopts an exact staged recovery/backlog notice when present, then sends
Expand Down
12 changes: 12 additions & 0 deletions crates/agent-spec/src/discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,18 @@ pub fn discover_strict(root: &Path) -> Discovered {
discover_impl(root, true)
}

/// Parse and lower one declaration file without walking its surrounding catalog.
///
/// `root` supplies the same path defaults as [`discover`]. The returned warnings describe only
/// this file.
pub fn discover_file(
root: &Path,
path: &Path,
) -> anyhow::Result<(Vec<AgentSpec>, Vec<String>)> {
let raws = parse_raw_file(path)?;
load_specs(root, path, raws)
}

fn discover_impl(root: &Path, strict: bool) -> Discovered {
DISCOVERY_WALK_COUNT.set(DISCOVERY_WALK_COUNT.get() + 1);
let mut out = Discovered::default();
Expand Down
Loading
Loading