Metadata-Version: 2.4
Name: agent-junction
Version: 0.1.0
Summary: Failover and credential-routing layer for AI agent stacks: probe agents with real requests, classify failures with a zero-token gate, switch on collapse, fail back on recovery.
Author: hypnoticproductions
License: MIT
Keywords: ai,agents,failover,high-availability,cli,opencode,llm
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Topic :: System :: Monitoring
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: psutil
Requires-Dist: psutil>=5.9; extra == "psutil"
Provides-Extra: laya
Requires-Dist: systemone>=0; extra == "laya"
Provides-Extra: test
Requires-Dist: pytest>=7; extra == "test"
Dynamic: license-file

# agent-junction

**Failover and credential-routing for AI agent stacks.** When one of your
AI coding agents dies — quota exhausted, key rate-limited, service down —
the junction detects it with a *real* probe, switches your work to a
healthy standby, and fails back when the primary recovers. The decision to
switch can be delegated to a zero-token classifier that holds on transient
blips and switches on real collapse.

```
   priority 0 (primary)          priority 1 (standby)
  ┌────────────────┐  collapse  ┌────────────────┐
  │  agent "hermes"│ ─────────► │ agent "opencode"│
  │  probe: tcp+proc│ ◄───────── │  probe: command │
  └────────────────┘  recovered └────────────────┘
              ▲                         ▲
              └───── junction tick ─────┘
              (cron every N minutes; no daemon required)
```

## Why

Anyone running two or more agent systems (OpenCode, Claude Code, Codex,
Gemini CLI, custom agents) eventually hits the same failure: an API key
runs out mid-task and work silently stops until a human notices. The
junction is the layer that notices for you — and it is agent-agnostic:
it doesn't care what your agents are, only how to prove they're alive.

| Capability | Naive alternative | agent-junction |
|---|---|---|
| Health check | `--version` ping (proves nothing) | live model round-trip, TCP probe, process scan |
| Failover decision | flip on first missed ping | optional zero-token gate classifies transient vs fatal |
| Safety | router failure = no failover | gate can delay a switch, never block one (streak bypass + fail-safe) |
| Forensics | "why did it switch?" | JSONL diagnostics + switch counter + calibrated decision trail |
| Ops model | another daemon to babysit | one bounded cron tick; no process to keep alive |

## Install

```bash
pip install agent-junction            # from PyPI (once published)
pip install "agent-junction[psutil]" # with process-probe support

# or straight from the repo:
pip install "git+https://github.com/hypnoticproductions/agent-junction.git"
```

## Quick start

1. Write a config (see `junction.example.json`):

```json
{
  "agents": [
    { "name": "hermes", "priority": 0, "tcp": "127.0.0.1:9119" },
    { "name": "opencode", "priority": 1,
      "command": ["opencode", "run", "--format", "json", "--model", "your/model", "Reply with the single word OK."],
      "expect": "OK" }
  ],
  "fail_streak_limit": 3,
  "state_file": "junction-state.json"
}
```

2. Probe and inspect:

```bash
junction --config junction.json status
junction --config junction.json doctor
```

3. Schedule the bounded tick (cron, Task Scheduler, systemd timer):

```
*/5 * * * *  junction --config /etc/junction/junction.json tick
*/5 * * * *  junction --config /etc/junction/junction.json failback
```

The tick is a single pass: probe active → maybe switch. No daemon, no
event loop, nothing to keep running between passes. Crashes are free.

## The decision gate

By default every failure switches immediately (`ImmediateSwitchGate`).
If you run a System One / Laya escalation head, the junction will consult
it first:

```json
"decision_gate": { "type": "laya", "module_path": "C:/path/to/chain", "escalate_threshold": 0.85 }
```

Two invariants hold no matter what the gate says:

1. **A gate can delay, never veto.** If the gate holds but the agent keeps
   failing, the streak counter force-switches after `fail_streak_limit`
   consecutive failures (~15 min at a 5-minute cadence).
2. **Gate failure = switch.** Any exception, missing chain, or placeholder
   decision resolves to *switch*. The decision layer is an optimization,
   not a dependency.

## Probes

| Probe | Config key | Proves |
|---|---|---|
| TCP | `"tcp": "host:port"` | a service is listening |
| Process | `"process": {"name": "...", "exe_contains": "..."}` | the daemon is running |
| Command | `"command": ["...", ...]`, `"expect": "OK"` | end-to-end: binary, credentials, backing API |

Stack probes on one agent for cheap-to-expensive checking: the first
listed is primary; the rest are fallbacks tried only when the primary
fails.

## Credentials

The junction never stores or logs credentials. Command probes inherit your
environment; load keys from your secret store in the cron wrapper, or
point `command` at a wrapper script that injects them. No key ever
appears in state, diagnostics, or the repo.

## Development

```bash
pip install -e ".[test,psutil]"
pytest
```

## Releasing

CI runs the test matrix (3 OSes × 4 Pythons) on every push. To publish a
release to PyPI:

1. Register the pending publisher once at
   [pypi.org/manage/account/publishing](https://pypi.org/manage/account/publishing/)
   — owner `hypnoticproductions`, repo `agent-junction`, workflow
   `publish.yml` (trusted publishing, no tokens stored anywhere).
2. Bump `version` in `pyproject.toml`, commit, then tag and push:
   `git tag v0.1.0 && git push origin v0.1.0`
3. The publish workflow runs tests, builds, checks, and publishes.
   Users then `pip install agent-junction`.

Until PyPI publishing is registered, users can install directly:

```bash
pip install "git+https://github.com/hypnoticproductions/agent-junction.git"
```

## License

MIT
