@ai-outfitter/outfitter 1.3.1 → 1.4.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.
@@ -4,6 +4,23 @@ export interface AgentProcessLauncher {
4
4
  }
5
5
  export declare const launchAgentProcess: (launcher: AgentProcessLauncher, launchPlan: AgentLaunchPlan, agentId: string) => Promise<number>;
6
6
  export declare const resolveAgentLaunchExecutable: (launchPlan: AgentLaunchPlan) => AgentLaunchPlan;
7
+ /**
8
+ * Forwards termination signals from this process to a spawned harness, resolving once the harness
9
+ * actually exits.
10
+ *
11
+ * Without this, a resident agent cannot shut down. Node installs no default forwarding, so the
12
+ * harness never learns the session is ending: under Kubernetes it is SIGKILLed when the grace period
13
+ * expires, skipping credential persistence and projection cleanup. The same gap shows up outside
14
+ * containers — Ctrl-C in a terminal, or a cancelled CI job — which is why this belongs here rather
15
+ * than in a container init.
16
+ *
17
+ * Installing a handler suppresses Node's default termination, so every path must resolve, and the
18
+ * listeners must come off afterwards or repeated launches in one process leak them.
19
+ */
20
+ export declare const attachSignalForwarding: (child: {
21
+ kill(signal?: NodeJS.Signals): boolean;
22
+ killed: boolean;
23
+ }, emitter?: NodeJS.EventEmitter, graceMs?: number) => (() => void);
7
24
  export declare const spawnLauncher: AgentProcessLauncher;
8
25
  /**
9
26
  * Launches a resolved plan through the given spawn boundary. The install-hint agentId is derived
@@ -1,6 +1,7 @@
1
1
  // Turns a logical agent launch plan into an actual launched process: resolves the bundled pi
2
2
  // binary, runs the launcher, and translates a missing agent CLI into actionable install guidance.
3
3
  import { existsSync, readFileSync } from 'node:fs';
4
+ import os from 'node:os';
4
5
  import { dirname, join } from 'node:path';
5
6
  import { fileURLToPath } from 'node:url';
6
7
  export const launchAgentProcess = async (launcher, launchPlan, agentId) => {
@@ -39,14 +40,64 @@ export const resolveAgentLaunchExecutable = (launchPlan) => {
39
40
  env: { PI_SKIP_VERSION_CHECK: '1', ...launchPlan.env },
40
41
  };
41
42
  };
43
+ // Signals we forward to the harness. SIGKILL is deliberately absent: it cannot be caught, and the
44
+ // kernel delivers it to us directly.
45
+ const FORWARDED_SIGNALS = ['SIGTERM', 'SIGINT', 'SIGHUP'];
46
+ // How long the harness gets to exit after a forwarded signal before we stop being polite. Kubernetes
47
+ // defaults to a 30s grace period and SIGKILLs the pod afterwards, so this has to be comfortably
48
+ // shorter or the escalation never runs.
49
+ const TERMINATION_GRACE_MS = 10_000;
50
+ /**
51
+ * Forwards termination signals from this process to a spawned harness, resolving once the harness
52
+ * actually exits.
53
+ *
54
+ * Without this, a resident agent cannot shut down. Node installs no default forwarding, so the
55
+ * harness never learns the session is ending: under Kubernetes it is SIGKILLed when the grace period
56
+ * expires, skipping credential persistence and projection cleanup. The same gap shows up outside
57
+ * containers — Ctrl-C in a terminal, or a cancelled CI job — which is why this belongs here rather
58
+ * than in a container init.
59
+ *
60
+ * Installing a handler suppresses Node's default termination, so every path must resolve, and the
61
+ * listeners must come off afterwards or repeated launches in one process leak them.
62
+ */
63
+ export const attachSignalForwarding = (child, emitter = process, graceMs = TERMINATION_GRACE_MS) => {
64
+ let escalation;
65
+ const forward = (signal) => () => {
66
+ if (child.killed)
67
+ return;
68
+ child.kill(signal);
69
+ // A harness that ignores or hangs on the signal would otherwise keep us alive until the
70
+ // orchestrator's own SIGKILL, losing the chance to exit cleanly first.
71
+ escalation ??= setTimeout(() => child.kill('SIGKILL'), graceMs);
72
+ escalation.unref?.();
73
+ };
74
+ const handlers = FORWARDED_SIGNALS.map((signal) => [signal, forward(signal)]);
75
+ for (const [signal, handler] of handlers)
76
+ emitter.on(signal, handler);
77
+ return () => {
78
+ for (const [signal, handler] of handlers)
79
+ emitter.removeListener(signal, handler);
80
+ if (escalation)
81
+ clearTimeout(escalation);
82
+ };
83
+ };
42
84
  /* v8 ignore start -- real process spawn is covered by end-to-end smoke usage, not unit tests. */
43
85
  export const spawnLauncher = {
44
86
  async launch(plan) {
45
87
  const { default: spawn } = await import('cross-spawn');
46
88
  return await new Promise((resolve, reject) => {
47
89
  const child = spawn(plan.command, [...plan.args], { stdio: 'inherit', env: { ...process.env, ...plan.env } });
48
- child.on('error', reject); // ENOENT surfaces as an actionable install message
49
- child.on('close', (code, signal) => resolve(code ?? (signal ? 1 : 0)));
90
+ const detach = attachSignalForwarding(child);
91
+ child.on('error', (error) => {
92
+ detach();
93
+ reject(error); // ENOENT surfaces as an actionable install message
94
+ });
95
+ child.on('close', (code, signal) => {
96
+ detach();
97
+ // 128+n is the shell convention for "died on signal n", and it is what a caller inspecting
98
+ // our exit status expects to see when the harness was terminated rather than returning.
99
+ resolve(code ?? (signal ? 128 + (os.constants.signals[signal] ?? 0) : 0));
100
+ });
50
101
  });
51
102
  },
52
103
  };
@@ -1 +1 @@
1
- {"version":3,"file":"AgentLaunch.js","sourceRoot":"","sources":["../../src/agents/AgentLaunch.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"AgentLaunch.js","sourceRoot":"","sources":["../../src/agents/AgentLaunch.ts"],"names":[],"mappings":"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"}
@@ -33,6 +33,7 @@ Outfitter lays out conventions for iterating on and sharing agent configuration
33
33
  The same composition runs on every surface; only the trigger changes.
34
34
 
35
35
  - [Running an agent in GitHub Actions](./actions.md) — headless runs on any workflow trigger.
36
+ - [Container images](./containers.md) — run the published image persistently or add Nix-packaged tools.
36
37
  - [Recurring runs](./recurring-runs.md) — loops three ways: the local loop extension, Actions cron, cluster schedules.
37
38
  - [In-cluster agents](./in-cluster.md) — resident agents, CronJobs, and subagent Jobs via Link Operator.
38
39
  - [Hooks](./hooks.md) — harness hook wiring and the protocol gap.
@@ -0,0 +1,138 @@
1
+ # Container images
2
+
3
+ The published image is a generic Outfitter runtime:
4
+
5
+ ```text
6
+ ghcr.io/ai-outfitter/outfitter:<version>
7
+ ```
8
+
9
+ It uses `outfitter` as its entrypoint and includes the Nix CLI, Bash, core
10
+ utilities, Git, SSH, and CA certificates. It does not include agent profiles,
11
+ credentials, channels, MCP servers, or other use-case behavior. The default
12
+ runtime user and group are both `1000`, with `/tmp` as the home directory.
13
+
14
+ ## Run a resident agent
15
+
16
+ A resident container is an ordinary `outfitter run` whose harness stays in RPC
17
+ mode. Keep standard input open so the harness remains available while its
18
+ extensions wait for work:
19
+
20
+ ```yaml
21
+ apiVersion: apps/v1
22
+ kind: Deployment
23
+ metadata:
24
+ name: example-agent
25
+ spec:
26
+ replicas: 1
27
+ selector:
28
+ matchLabels:
29
+ app: example-agent
30
+ template:
31
+ metadata:
32
+ labels:
33
+ app: example-agent
34
+ spec:
35
+ securityContext:
36
+ # Pod-level, and not optional. HOME points at the mounted volume, and a
37
+ # freshly provisioned volume arrives owned by root — runAsUser does not
38
+ # change that. Extension installs and credential persistence both write
39
+ # below HOME, so without fsGroup the agent fails with EACCES on first
40
+ # write rather than at startup. A volume plugin that ignores fsGroup
41
+ # must be pre-provisioned with UID/GID 1000 ownership instead.
42
+ fsGroup: 1000
43
+ containers:
44
+ - name: agent
45
+ image: ghcr.io/ai-outfitter/outfitter:<version>
46
+ stdin: true
47
+ workingDir: /workspace
48
+ securityContext:
49
+ runAsNonRoot: true
50
+ runAsUser: 1000
51
+ runAsGroup: 1000
52
+ env:
53
+ - name: HOME
54
+ value: /workspace
55
+ args:
56
+ - run
57
+ - example-agent
58
+ - --strict
59
+ - --
60
+ - --mode
61
+ - rpc
62
+ - --no-session
63
+ volumeMounts:
64
+ - name: workspace
65
+ mountPath: /workspace
66
+ volumes:
67
+ - name: workspace
68
+ persistentVolumeClaim:
69
+ claimName: example-agent
70
+ ```
71
+
72
+ Mount `.agents` settings, credentials, and any channel configuration through
73
+ the workload that owns the container. The image does not interpret Kubernetes
74
+ resources or impose image, profile, or extension policy.
75
+
76
+ ## Build a derivative image
77
+
78
+ Being built with Nix does not normally imply that an image contains the Nix
79
+ CLI. The published Outfitter image includes it intentionally, and the flake also
80
+ exports `lib.mkContainer` for reproducible derivative images:
81
+
82
+ ```nix
83
+ {
84
+ inputs = {
85
+ nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
86
+ outfitter = {
87
+ url = "github:ai-outfitter/outfitter/v1.2.0";
88
+ inputs.nixpkgs.follows = "nixpkgs";
89
+ };
90
+ };
91
+
92
+ outputs =
93
+ { nixpkgs, outfitter, ... }:
94
+ let
95
+ system = "x86_64-linux";
96
+ pkgs = nixpkgs.legacyPackages.${system};
97
+ in
98
+ {
99
+ packages.${system}.default = outfitter.lib.mkContainer {
100
+ inherit pkgs;
101
+ outfitterPackage = outfitter.packages.${system}.outfitter;
102
+ name = "example-agent";
103
+ extraPackages = [
104
+ pkgs.jq
105
+ pkgs.ripgrep
106
+ ];
107
+ };
108
+ };
109
+ }
110
+ ```
111
+
112
+ Build and exercise the exact image:
113
+
114
+ ```sh
115
+ nix build
116
+ docker load < result
117
+ docker run --rm example-agent:latest --version
118
+ docker run --rm --entrypoint /bin/sh example-agent:latest \
119
+ -c 'nix --version && jq --version && rg --version'
120
+ ```
121
+
122
+ Prefer adding known runtime packages through `extraPackages`. The resulting
123
+ image stays reproducible, and it avoids the trap below.
124
+
125
+ **Do not mount an empty volume over `/nix`.** The image _is_ its Nix store: the
126
+ entrypoint is an absolute store path and every binary in `/bin` is a symlink
127
+ into `/nix/store`. Mounting a fresh volume there hides all of it, so the
128
+ container cannot start — it fails before it could initialize the very store you
129
+ mounted the volume to populate.
130
+
131
+ Runtime installation therefore needs one of:
132
+
133
+ - a volume **pre-populated** with the image's closure, seeded from the image
134
+ before the agent starts (an init container copying `/nix` into the volume);
135
+ - an **overlay** whose lower layer is the image's `/nix`, so the closure stays
136
+ visible while writes land in the upper layer; or
137
+ - writable Nix **state** only — `/nix/var` and a per-user profile — leaving the
138
+ store itself as the image shipped it.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-outfitter/outfitter",
3
- "version": "1.3.1",
3
+ "version": "1.4.0",
4
4
  "description": "Profile-oriented wrapper for launching pi, Claude Code, and future agent CLIs with reproducible configuration.",
5
5
  "type": "module",
6
6
  "repository": {