diff --git a/tools/subagent_generate b/tools/subagent_generate index 864d58f..aa27699 100755 --- a/tools/subagent_generate +++ b/tools/subagent_generate @@ -1,64 +1,98 @@ -#!/usr/bin/env bash -# description: Generate a JIT subagent config. No args prints the JSON template. With JSON on stdin, creates the config and prints the agent name. +#!/usr/bin/env python3 +# description: Generate a JIT subagent config. No args prints the JSON template. With JSON as first arg or on stdin, creates the config and prints the agent name. # Args: [json] — JSON config as first arg, or piped via stdin -# Stdin: JSON with structured fields (role, roster, shared_task, protocol, constraints) or legacy "prompt" -# Output: template (no stdin) or agent name -set -euo pipefail +# Output: template (no input) or agent name -OLLIE="${OLLIE:-$HOME/mnt/ollie}" +import json +import os +import secrets +import sys -die() { printf 'agent_generate: %s\n' "$*" >&2; exit 1; } +OLLIE = os.environ.get("OLLIE", os.path.expanduser("~/mnt/ollie")) +AGENTS_DIR = f"{OLLIE}/a" +BASE_CONFIG = f"{AGENTS_DIR}/default.json" -if [ -t 0 ] && [ $# -eq 0 ]; then - printf '{\n "name": "",\n "role": "",\n "roster": "",\n "shared_task": "",\n "protocol": "",\n "constraints": [],\n "prompt": ""\n}\n' - exit 0 -fi +TEMPLATE = { + "name": "", + "role": "", + "roster": "", + "shared_task": "", + "protocol": "", + "constraints": [], + "prompt": "", +} -if [ $# -gt 0 ]; then - cfg="$1" -else - cfg=$(cat) || die "failed to read stdin" -fi -[ -z "$cfg" ] && die "empty input" -name=$(printf '%s' "$cfg" | python3 -c " -import json, sys, secrets - -raw = json.load(sys.stdin) -name = raw.get('name', '') or 'jit-' + secrets.token_hex(8) - -role = raw.get('role', '') -roster = raw.get('roster', '') -shared_task = raw.get('shared_task', '') -protocol = raw.get('protocol', '') -constraints = raw.get('constraints', []) -legacy = raw.get('prompt', '') - -if not role and not legacy: - print('error: role or prompt required', file=sys.stderr) +def die(msg): + print(f"subagent_generate: {msg}", file=sys.stderr) sys.exit(1) -if role: - parts = ['## Role', role] - if roster: parts += ['## Roster', roster] - if shared_task: parts += ['## Shared Task', shared_task] - if protocol: parts += ['## Coordination Protocol', protocol] - if constraints: parts += ['## Constraints', '\n'.join('- ' + c for c in constraints)] - identity = '\n\n'.join(parts) -else: - identity = legacy -base = sys.argv[1] -with open(base) as f: - cfg = json.load(f) +def main(): + # No input: print template. + if len(sys.argv) < 2 and sys.stdin.isatty(): + json.dump(TEMPLATE, sys.stdout, indent=2) + print() + return -cfg['prompt'] = ['\$OLLIE/x/prime SYSTEM_PROMPT', 'echo ' + json.dumps(identity)] + # Read config from arg or stdin. + if len(sys.argv) > 1: + text = sys.argv[1] + else: + text = sys.stdin.read() + if not text.strip(): + die("empty input") -out = sys.argv[2] + '/' + name + '.json' -with open(out, 'w') as f: - json.dump(cfg, f, indent=2) + try: + raw = json.loads(text) + except json.JSONDecodeError as e: + die(f"invalid JSON: {e}") -print(name) -" "$OLLIE/a/default.json" "$OLLIE/a") || die "failed to generate agent config" + name = raw.get("name", "") or f"jit-{secrets.token_hex(8)}" + role = raw.get("role", "") + roster = raw.get("roster", "") + shared_task = raw.get("shared_task", "") + protocol = raw.get("protocol", "") + constraints = raw.get("constraints", []) + legacy = raw.get("prompt", "") -printf '%s\n' "$name" + if not role and not legacy: + die("role or prompt required") + + # Build identity text. + if role: + parts = ["## Role", role] + if roster: + parts += ["## Roster", roster] + if shared_task: + parts += ["## Shared Task", shared_task] + if protocol: + parts += ["## Coordination Protocol", protocol] + if constraints: + parts += ["## Constraints", "\n".join(f"- {c}" for c in constraints)] + identity = "\n\n".join(parts) + else: + identity = legacy + + # Load base config. + try: + with open(BASE_CONFIG) as f: + cfg = json.load(f) + except (OSError, json.JSONDecodeError) as e: + die(f"failed to read base config: {e}") + + cfg["prompt"] = ["$OLLIE/x/prime SYSTEM_PROMPT", f"echo {json.dumps(identity)}"] + + # Write JIT config. + out_path = f"{AGENTS_DIR}/{name}.json" + try: + with open(out_path, "w") as f: + json.dump(cfg, f, indent=2) + except OSError as e: + die(f"failed to write config: {e}") + + print(name) + + +if __name__ == "__main__": + main() diff --git a/tools/subagent_spawn b/tools/subagent_spawn index df78426..78b9401 100755 --- a/tools/subagent_spawn +++ b/tools/subagent_spawn @@ -1,7 +1,7 @@ #!/usr/bin/env bash # description: Spawn a subagent session, submit a prompt, and return immediately. The subagent runs independently; use prompt files for coordination. # Args: [-name NAME] [-agent A] [-backend B] [-model M] [-cwd DIR] -# Output: session path (e.g. s/my-session) +# Output: structured KV (session, path, backend, model) set -euo pipefail OLLIE="${OLLIE:-$HOME/mnt/ollie}"