agents-harness
Declarative scheduled flow runner, per-request agent loop, and job kernel for local agent services.
Zero heavy dependencies (Python standard library only). No Celery, no Redis, no cron daemons.
Quickstart
pip install agents-harness
Optional FastMCP server support:
pip install "agents-harness[mcp]"
Optional trace sealing and audit (--seal, mcp.traces.seal, mcp.traces.audit) need agents-traces 0.1.0 or newer:
pip install "agents-harness[traces]"
Calendar tools (mcp.calendar.add, mcp.calendar.list) forward --calendar, which needs agents-calendar 0.1.0 or newer.
Architecture
| Layer | Responsibility | Components |
|---|---|---|
| Agent Loop | Per-request reconstructed trace & tool turns | runner.loop |
| Job Kernel | Declarative Cordis catalog & deterministic execution | runner.kernel, runner.cordis_tools |
| Schedules | Manifest-defined care flows & dynamic reminders | runner.executor, runner.schedule |
| Providers | Streaming completions & OpenAI-compatible tools | runner.providers |
| Identity | Decoupled alias, user, and session directory | ~/.agents/identity.json |
- Per-request loop (
runner.loop): Reconstructs conversation traces on demand. Cordis modules (runner/modules/*.json) map as tools into OpenAI-compatible tool calling rounds. - Strict Layering (Cordis Principle): The executor owns no domain state or complex semantics. It strictly executes declared CLI verbs and inspects exit codes (
0 = healthy). - Durable Manifests (Koru Principle): Scheduled tasks are declared in
runner/schedules/*.json. Dynamic reminders are managed viarunner.schedule. - Zero Bloat: Pure Python standard library (
subprocess,json,pathlib,urllib).
Capabilities & Roadmap
Core Capabilities (Implemented)
- Per-turn Execution Loop (
runner.loop): Stateless reconstructed conversation trace per turn. - Cordis CLI Tool Round: Declared CLI verbs (
runner/modules/*.json) mapped as OpenAI-compatible function tools. - Job Kernel (
runner.kernel): Declarative AST checking, mixing, and deterministic term reduction (norm,konst,comp,app). - Koru Schedules (
runner.executor): Declarative scheduled flow manifests (runner/schedules/*.json) with exit code health semantics (exit 0 = healthy). - Dynamic Reminders (
runner.schedule): Dynamic one-shot, cron, and routine jobs.tick()is in-process safe (file lock, timezone, grace window) and still callable from host cron. - Approval gate:
AGENTS_APPROVAL_CMD/AGENTS_APPROVAL_MODEbefore mutatingcall_jobtools. - Untrusted tool fence: tool results are wrapped for the model; redaction stays.
- External MCP client:
~/.agents/mcp.jsontools show up asmcp.<server>.<tool>. - Skills:
skill.list/skill.loadread~/.agents/skills/*/SKILL.md. - Attachments:
--attachsends images as vision parts when vision is enabled. - Provider Streaming: Streaming completions with prefill (
first_byte_sec) and stall (idle_sec) hang detection. - Decoupled Identity: Decoupled alias (
telegram:123), user (u_...), and session (ses_...) directory. - FastMCP Interface (
runner.mcp_server): Exposeslist_catalog,load_schema, andcall_jobfor MCP hosts. - Zero Bloat Runtime: Pure Python standard library only (no Celery, no Redis, no cron daemons).
Planned (Roadmap)
- Formal Kernel Verification: Lean 4 formalization export of Cordis job combinators.
- Decentralized Identity (DID): Native Ed25519
did:keyresolution and proof exchange. - Multi-Agent Orchestration: A2A peer execution mesh with cryptographically signed task mailboxes.
- Adaptive Cadence: Self-tuning care flow intervals based on host execution history.
CLI & Modules
| Command / Entrypoint | Description |
|---|---|
agents-harness (runner.executor) |
Run and validate scheduled flows (--all, --run <name>, --validate) |
agents-harness-loop (runner.loop) |
Per-request agent loop (--complete, --assemble-only, --call <verb>) |
agents-harness-kernel (runner.kernel) |
Inspect and reduce declarative Cordis job terms (--alphabet, --check, --reduce) |
agents-harness-mcp (runner.mcp_server) |
FastMCP server exposing list_catalog, load_schema, and call_job |
python -m runner.schedule |
Dynamic schedule runner (add, list, remove, tick) |
Example Commands
# Validate all scheduled manifests
python -m runner.executor --validate
# Run all scheduled tasks once
python -m runner.executor --all
# List discovered Cordis modules and tool bindings
python -m runner.loop --list-modules
python -m runner.loop --list-tools
python -m runner.loop --list-providers
# Assemble a turn (context + prompt) without calling provider
python -m runner.loop --user 123 --message "hello" --assemble-only
# Complete request with a provider
python -m runner.loop --user probe --message "status report" --complete --provider openai.default
# Tick dynamic due reminders (also safe to call in-process from klanker serve)
python -m runner.schedule tick
# Assemble a turn with an image (vision parts when AGENTS_VISION=1)
python -m runner.loop --user 123 --message "what is this?" --attach ./shot.png --assemble-only
Approval, skills, schedules, attachments
Mutating call_job tools ask first when AGENTS_APPROVAL_CMD is set. {user} in that command becomes the turn's channel user. AGENTS_APPROVAL_MODE=ask gates mutators (the default once a command is set). Creating or listing a schedule does not ask in ask mode; removing one does. strict gates every tool that is not read-only, including schedule create. off, or no command, keeps the old behavior. Exit 0 approves, 1 denies, 2 is timeout/unavailable (denied). A denial tells the model not to retry.
Every tool result the model sees is wrapped in <untrusted_data source="...">...</untrusted_data> after redaction. Only messages the harness builds itself (approval denials, loop notices) stay outside that fence; tool text that merely starts with Denied: is still fenced.
Skills live in ~/.agents/skills/<name>/SKILL.md (AGENTS_SKILLS_DIR, plus AGENTS_SKILLS_EXTRA). The system prompt lists name and description. skill.load returns the file. skill.catalog is a compatibility alias of skill.list.
Cron uses the job's timezone (else AGENTS_TIMEZONE, TZ, or timezone in ~/.agents/config.json). The last fired minute is stored next to the manifests so a double tick does not double-fire, and a miss inside grace_min (default 5) still runs once. LLM reminders and routines default to timeout_sec 300. tick() holds its file lock only while claiming or finalizing, then runs jobs outside the lock. tick(wait=False) returns after the claim; python -m runner.schedule tick waits. A failed or timed-out one-shot is removed and not retried. The failure is logged, and last_result stays in tick-state.json. Routine jobs (--prompt) are not executed here: register runner.schedule.register_routine_handler. With no handler, a routine that has a verb runs that verb; a routine with only a prompt is skipped and its slot or one-shot file is left in place. mcp.schedule.add copies the turn's channel and user onto the job when the call omits them.
python -m runner.loop --detached-session --session ses_… runs that turn in ses_… and restores the previous active session afterward. A session id that starts with routine: is detached the same way without the flag. A host cron or a platform scheduler can call python -m runner.schedule tick when it runs as the same user as the service. That process does not inherit environment set only on the long-running service (for example AGENTS_APPROVAL_CMD).
| Variable | Default | Effect |
|---|---|---|
AGENTS_APPROVAL_TIMEOUT |
--timeout in the command + 15 s, else 330 s |
Seconds to wait for the approval command before denying |
AGENTS_SCHEDULE_GRACE_MIN |
5 | Minutes a missed cron slot still runs once (a job's grace_min wins) |
AGENTS_SCHEDULES_DIR |
~/.agents/schedules |
Dynamic reminder manifests; also an overlay for runner.executor schedules |
AGENTS_BUNDLED_SCHEDULES |
1 |
0/false/no/off hides the bundled runner/schedules/ from runner.executor |
External MCP client
~/.agents/mcp.json (override AGENTS_MCP_CONFIG) uses the Claude/Cursor mcpServers shape, plus optional allow / deny globs. ${ENV_VAR} expands in commands, args, env, urls, and headers. See mcp.client.json.example.
Each server tool is a catalog module mcp.<server>.<tool> (kind: mcp_remote) with the server's inputSchema. readOnlyHint: true is non-mutating; anything else is a mutator for the approval gate. Hand-written modules of the same name win.
The loop process is new every turn. Schemas are cached for AGENTS_MCP_CACHE_SEC (default 60) in AGENTS_MCP_CACHE (default ~/.agents/mcp-tools-cache.json). AGENTS_MCP_CONNECT_TIMEOUT (default 10 s) bounds each listing or call session, connect included. Each call connects, initializes, calls one tool, and disconnects (stdio, streamable HTTP, or SSE). Install the SDK with pip install "agents-harness[mcp]". OAuth is out of scope.
MCP Server Integration
To use the harness as an MCP server for Claude, Cursor, or Antigravity:
{
"mcpServers": {
"agents-harness": {
"command": "python",
"args": ["-m", "runner.mcp_server"]
}
}
}
Exposes:
list_catalog: List Cordis job modules available on this machine.load_schema: Load parameter schema for a catalog module.call_job: Safely execute a catalog module by name (unknown names fail closed).
Tests
# Validate manifests
python -m runner.executor --validate
# Run test suite
python -m unittest discover tests
License
MIT. See LICENSE.
Metadata
Release files for agents-harness 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| agents_harness-0.1.0.tar.gz | 77.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| agents_harness-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 156.8 kB
Release files / agents_harness-0.1.0.tar.gz
| Download URL | agents_harness-0.1.0.tar.gz |
|---|---|
| Size | 77.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
819517bcfb5912db0af2de33520d815f330f6b9b92343d17f65ee8de54e009bb
|
|
BLAKE2b-256 checksum How to use checksums |
1c9c29e9edf94d572eb80200882a5aec728e0b76ff04c3b88a6753bf02233e56
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / agents_harness-0.1.0-py3-none-any.whl
| Download URL | agents_harness-0.1.0-py3-none-any.whl |
|---|---|
| Size | 79.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ee38fd171821196f75e187cf40bb0f68e0c7f18c0ff787c008ee78ccc5145467
|
|
BLAKE2b-256 checksum How to use checksums |
454aad9292b5d65f45eba6bf2784f53870d95559d86a842ac5036799b1eafeb1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|