ace-herdr 0.9.0 → 0.10.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/docs/native-source.md +44 -0
- data/docs/usage.md +23 -0
- data/lib/ace/herdr/cli/commands/dispatch.rb +3 -1
- data/lib/ace/herdr/molecules/herdr_executor.rb +16 -5
- data/lib/ace/herdr/molecules/installed_pi_extensions.rb +60 -0
- data/lib/ace/herdr/native_source/guarded-prompt.patch +1777 -1
- data/lib/ace/herdr/native_source/selection.json +4 -4
- data/lib/ace/herdr/native_source/zig-linker +3 -0
- data/lib/ace/herdr/organisms/control_surface.rb +5 -2
- data/lib/ace/herdr/organisms/dispatcher.rb +9 -9
- data/lib/ace/herdr/organisms/native_source_builder.rb +30 -9
- data/lib/ace/herdr/version.rb +1 -1
- metadata +11 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 204e5cf8135c02f3feea7086b05a2b3dad61470261dbd526a05b900dd1f5e558
|
|
4
|
+
data.tar.gz: b362791611a61f9340055ad044383dd4585b2d5821ffc1d28a4e7356dd6ab6b1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8c564329116783969ae79dc7256f8a414591e308d18b3b8772b751ac1efea5d07b8f4758ee34b23134b50631a571e01d48d7872b20c8510fb637d019f6889b42
|
|
7
|
+
data.tar.gz: 7a1be61896fff3f5ca58c7ee00d2c4fa0633e935805c2cd466ceaad12b4a4a3bf04cbf02d7cca7bd43bf3edf28196feb190b14e3282fe736a8e6137e5918a45f
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,14 @@ All notable changes to `ace-herdr` will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.10.0] - 2026-11-10
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Launch ordinary agents with an explicit private JSON environment file, without exposing credentials in terminal commands. Preserve real pane/session identity and refuse cold auto-resume without the original environment. Retain protected installed extensions and package reproducible native source builds for Linux ARM64, including cross-building on macOS.
|
|
15
|
+
|
|
8
16
|
## [0.9.0] - 2026-10-30
|
|
9
17
|
|
|
10
18
|
### Changed
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Building the selected native Herdr
|
|
2
|
+
|
|
3
|
+
`ace-herdr native-source selection` reports the source commit, tree and patch
|
|
4
|
+
digest packaged with this gem. The patch retains ordinary prompt/terminal
|
|
5
|
+
identity fixes and adds explicit process environments. It does not install an
|
|
6
|
+
issuer, systemd session launcher or separate operating-system identity.
|
|
7
|
+
|
|
8
|
+
Build with the public owner command:
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
ace-herdr native-source build --source /absolute/clean/herdr-checkout \
|
|
12
|
+
--output /absolute/new/artifact-directory --target aarch64-unknown-linux-musl
|
|
13
|
+
ace-herdr native-source verify --artifact /absolute/new/artifact-directory
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The build runs as a nonroot user on Linux or macOS. Install the selected Rust
|
|
17
|
+
toolchain, its target standard library, and the selected Zig version first.
|
|
18
|
+
Use actual tool executables in `PATH`, rather than shims which resolve another
|
|
19
|
+
version. The packaged linker invokes that same pinned Zig for the selected
|
|
20
|
+
Linux target; a VM and a separate cross compiler are unnecessary. Output
|
|
21
|
+
contains the ELF executable and its source/toolchain/byte provenance. A failed
|
|
22
|
+
build retains its output directory for diagnosis and never overwrites it.
|
|
23
|
+
|
|
24
|
+
The selected native CLI accepts `herdr agent start … --env-file /absolute/env.json`.
|
|
25
|
+
The file must be a regular, non-symlink file owned by the process user with mode
|
|
26
|
+
`0600`, containing a JSON object of environment variable names and string values.
|
|
27
|
+
The ordinary child process receives that environment, plus native terminal
|
|
28
|
+
identity (`HERDR_*` pane/workspace/socket fields and the ACE pane/session aliases).
|
|
29
|
+
Ambient credentials are cleared. File contents are never submitted to the
|
|
30
|
+
terminal or returned in the start response. The caller removes the file after
|
|
31
|
+
startup returns. With `ACE_SESSION_CREDENTIALS_DIRECTORY` equal to
|
|
32
|
+
`PI_CODING_AGENT_DIR`, a same-user `0700` directory named `ace-session-*`, the
|
|
33
|
+
ordinary helper waits for the child and removes only `auth.json` on exit or
|
|
34
|
+
failed startup. Session evidence remains. TERM/HUP are forwarded to the child;
|
|
35
|
+
terminal Ctrl-C reaches its existing foreground process group. An uncatchable
|
|
36
|
+
kill can leave credentials behind, so the assignment/coordinator owner must
|
|
37
|
+
reconcile them before replacing a dead session. This is cleanup between trusted
|
|
38
|
+
processes, not isolation between hostile processes sharing the same user.
|
|
39
|
+
|
|
40
|
+
Cold server restoration keeps such a pane visibly unavailable until its
|
|
41
|
+
coordinator supplies a fresh environment. It never resumes an agent through
|
|
42
|
+
an ambient shell. Live terminal handoff preserves the already-running process.
|
|
43
|
+
|
|
44
|
+
Host tests and a cross-built ELF do not establish live ARM64 lab acceptance.
|
data/docs/usage.md
CHANGED
|
@@ -70,6 +70,24 @@ by the number of deliveries.
|
|
|
70
70
|
|
|
71
71
|
## Dispatch, send, capture, wait, close
|
|
72
72
|
|
|
73
|
+
`dispatch` starts an ordinary process in `--cwd`. For an explicitly selected
|
|
74
|
+
environment, pass `--env-file` with an absolute path to a private (`0600`) JSON
|
|
75
|
+
object of string variables. The native launcher clears ambient variables and
|
|
76
|
+
reads this file without placing its values in terminal commands or prompts.
|
|
77
|
+
`--role` labels the commission; it does not grant operating-system privileges.
|
|
78
|
+
|
|
79
|
+
```sh
|
|
80
|
+
ace-herdr dispatch --label review-task --role verifier --cwd /lab/ace/.ace-wt/task-1 --env-file /private/role-environment.json --prompt-file commission.md
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Installed Linux deployments use the protected extensions configured by
|
|
84
|
+
`ace-lab`. Missing required runtime or extension files block startup. Sessions
|
|
85
|
+
of the same user are trusted peers, not a hostile-code sandbox. For task work,
|
|
86
|
+
use `ace-overseer work-on` or `handoff`: these own commission identity, duplicate
|
|
87
|
+
protection and report-driven retirement. A low-level dispatch alone is not an
|
|
88
|
+
assignment lifecycle.
|
|
89
|
+
|
|
90
|
+
|
|
73
91
|
```bash
|
|
74
92
|
ace-herdr dispatch --label t.123 --kind pi --prompt-file prompt.md
|
|
75
93
|
ace-herdr dispatch --label review --pane p7 --cwd /Users/me/project
|
|
@@ -87,6 +105,11 @@ prompt semantics: text becomes one self-submitting agent prompt, and a single
|
|
|
87
105
|
trailing Enter is dropped and reported. A command must be declared before any
|
|
88
106
|
key.
|
|
89
107
|
|
|
108
|
+
For the native `herdr agent prompt` command, `--wait` without `--until`
|
|
109
|
+
waits for a settled state. Use `--wait --until working --until blocked` to
|
|
110
|
+
wait for observed activity instead. A stalled or timed-out prompt has an
|
|
111
|
+
uncertain delivery outcome; inspect the existing agent before retrying.
|
|
112
|
+
|
|
90
113
|
## Workspaces, tabs, and presets
|
|
91
114
|
|
|
92
115
|
Presets resolve through the ADR-022 cascade (`.ace/herdr/` > `~/.ace/herdr/` >
|
|
@@ -26,6 +26,8 @@ module Ace
|
|
|
26
26
|
]
|
|
27
27
|
|
|
28
28
|
option :label, type: :string, desc: "Agent and pane label (typically the task id)"
|
|
29
|
+
option :role, type: :string, desc: "Executor role label"
|
|
30
|
+
option :env_file, type: :string, desc: "Private JSON file containing the executor environment"
|
|
29
31
|
option :kind, type: :string, desc: "Agent kind (default: config default_agent_kind)"
|
|
30
32
|
option :workspace, type: :string, desc: "Target workspace (default: caller's workspace)"
|
|
31
33
|
option :pane, type: :string, desc: "Existing pane (default: create a new tab)"
|
|
@@ -50,7 +52,7 @@ module Ace
|
|
|
50
52
|
outcome = dispatcher.dispatch(
|
|
51
53
|
label: options.fetch(:label), prompt: prompt,
|
|
52
54
|
kind: options[:kind], workspace_id: options[:workspace],
|
|
53
|
-
pane: options[:pane], cwd: options[:cwd]
|
|
55
|
+
pane: options[:pane], cwd: options[:cwd], role: options[:role], env_file: options[:env_file]
|
|
54
56
|
)
|
|
55
57
|
puts JSON.generate(outcome.to_h)
|
|
56
58
|
end
|
|
@@ -5,6 +5,7 @@ require "open3"
|
|
|
5
5
|
require "json"
|
|
6
6
|
require "timeout"
|
|
7
7
|
require_relative "bounded_process"
|
|
8
|
+
require_relative "installed_pi_extensions"
|
|
8
9
|
|
|
9
10
|
module Ace
|
|
10
11
|
module Herdr
|
|
@@ -23,6 +24,10 @@ module Ace
|
|
|
23
24
|
@process = process
|
|
24
25
|
end
|
|
25
26
|
|
|
27
|
+
def agent_list
|
|
28
|
+
run!([@binary, "agent", "list"])
|
|
29
|
+
end
|
|
30
|
+
|
|
26
31
|
# Probe the agent living in a pane (raises AgentNotFoundError when none)
|
|
27
32
|
def agent_get(pane)
|
|
28
33
|
run!([@binary, "agent", "get", pane])
|
|
@@ -55,9 +60,13 @@ module Ace
|
|
|
55
60
|
# `args` are harness passthrough arguments forwarded verbatim
|
|
56
61
|
# after `--` (e.g. pi's --model/--thinking/--append-system-prompt;
|
|
57
62
|
# the overseer start contract, 8xs.t.pu0.1).
|
|
58
|
-
def agent_start(name:, kind:, pane:, timeout_ms:, args: [])
|
|
63
|
+
def agent_start(name:, kind:, pane:, timeout_ms:, args: [], env_file: nil)
|
|
64
|
+
managed = kind == "pi" ? InstalledPiExtensions.arguments : []
|
|
65
|
+
InstalledPiExtensions.reject_override!(args) unless managed.empty?
|
|
66
|
+
args = managed + args
|
|
59
67
|
cmd = [@binary, "agent", "start", name, "--kind", kind,
|
|
60
68
|
"--pane", pane, "--timeout", timeout_ms.to_s]
|
|
69
|
+
cmd += ["--env-file", env_file] if env_file
|
|
61
70
|
cmd += ["--", *args] unless args.empty?
|
|
62
71
|
run!(cmd)
|
|
63
72
|
end
|
|
@@ -66,12 +75,14 @@ module Ace
|
|
|
66
75
|
# rejects blocked agents pre-send with agent_blocked. With
|
|
67
76
|
# wait: herdr's activity gate applies — a prompt sent from a
|
|
68
77
|
# non-working state must produce observed working/blocked
|
|
69
|
-
# activity
|
|
70
|
-
#
|
|
71
|
-
|
|
72
|
-
|
|
78
|
+
# activity before waiting for the requested state. With no explicit
|
|
79
|
+
# states the native default waits for idle, done or blocked.
|
|
80
|
+
def agent_prompt(pane:, text:, wait: false, timeout_ms: nil, until_states: nil)
|
|
81
|
+
raise ArgumentError, "until_states requires wait" if until_states && !wait
|
|
82
|
+
|
|
73
83
|
cmd = [@binary, "agent", "prompt", pane, text]
|
|
74
84
|
cmd += ["--wait"] if wait
|
|
85
|
+
Array(until_states).each { |state| cmd += ["--until", state] }
|
|
75
86
|
cmd += ["--timeout", timeout_ms.to_s] if timeout_ms
|
|
76
87
|
run!(cmd)
|
|
77
88
|
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "find"
|
|
4
|
+
require "yaml"
|
|
5
|
+
require "ace/runtime"
|
|
6
|
+
require "ace/runtime/molecules/protected_socket"
|
|
7
|
+
require_relative "../errors"
|
|
8
|
+
|
|
9
|
+
module Ace
|
|
10
|
+
module Herdr
|
|
11
|
+
module Molecules
|
|
12
|
+
# Installed Linux lab agents load only host-managed Pi extensions.
|
|
13
|
+
# The existing root-owned lab manifest declares their location; no
|
|
14
|
+
# second registry or agent-writable discovery path is introduced.
|
|
15
|
+
module InstalledPiExtensions
|
|
16
|
+
MANIFEST_DIR = "/etc/lab/ace-lab"
|
|
17
|
+
MANIFEST = "#{MANIFEST_DIR}/install.yml"
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
def arguments(env: ENV, manifest: MANIFEST, manifest_dir: MANIFEST_DIR, platform: RUBY_PLATFORM)
|
|
21
|
+
return [] unless env["ACE_TOOLCHAIN"] == "installed" && platform.include?("linux")
|
|
22
|
+
return [] unless File.exist?(manifest_dir) || File.symlink?(manifest_dir)
|
|
23
|
+
|
|
24
|
+
guard = Ace::Runtime::Molecules::ProtectedSocket
|
|
25
|
+
guard.root_path!(manifest, directory: false)
|
|
26
|
+
raise ExecutorUnavailableError, "installed lab manifest is not a regular file" unless File.file?(manifest)
|
|
27
|
+
document = YAML.safe_load(File.read(manifest), permitted_classes: [], aliases: false)
|
|
28
|
+
rows = document.is_a?(Hash) && document.dig("gemset", "extensions")
|
|
29
|
+
matches = rows.is_a?(Array) ? rows.select { |row| row.is_a?(Hash) && row["gem"] == "ace-wake" } : []
|
|
30
|
+
raise ExecutorUnavailableError, "installed lab must declare exactly one ace-wake extension path" unless matches.size == 1
|
|
31
|
+
root = matches.fetch(0)["path"]
|
|
32
|
+
guard.root_path!(root, directory: true)
|
|
33
|
+
wake = File.join(root, "ace-wake")
|
|
34
|
+
guard.root_path!(wake, directory: true)
|
|
35
|
+
Find.find(wake) do |path|
|
|
36
|
+
stat = File.lstat(path)
|
|
37
|
+
unless (stat.file? || stat.directory?) && !stat.symlink? && stat.uid.zero? && (stat.mode & 0o022).zero?
|
|
38
|
+
raise ExecutorUnavailableError, "installed ace-wake contains an unsafe path"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
entry = File.join(wake, "index.js")
|
|
42
|
+
integration = File.join(root, "herdr-agent-state.ts")
|
|
43
|
+
[entry, integration].each do |path|
|
|
44
|
+
guard.root_path!(path, directory: false)
|
|
45
|
+
raise ExecutorUnavailableError, "installed Pi extension is not a regular file" unless File.file?(path)
|
|
46
|
+
end
|
|
47
|
+
["--no-extensions", "--extension", entry, "--extension", integration]
|
|
48
|
+
rescue Ace::Runtime::Error, SystemCallError, Psych::Exception, TypeError, ArgumentError
|
|
49
|
+
raise ExecutorUnavailableError, "installed Pi extension configuration is unavailable or unsafe"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def reject_override!(args)
|
|
53
|
+
return if args.none? { |arg| arg.is_a?(String) && (arg == "--no-extensions" || arg == "-ne" || arg.start_with?("--extension=") || arg == "--extension" || arg.start_with?("-e")) }
|
|
54
|
+
|
|
55
|
+
raise ExecutorUnavailableError, "installed lab Pi extension arguments cannot be overridden"
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|