Skip to main content

agentknock

Your terminal agents, knocking each other awake while you watch — without stealing focus. It rings the doorbell; it never gives the orders.

English | 日本語

agentknock (module: console_injector) is a transport layer for desktop multi-agent orchestration. It wakes an interactive CLI/TUI AI agent (e.g. Antigravity agy.exe, and — via adapters — other terminal agents) that is blocked on stdin while idle, by injecting a keystroke into the target process's console input buffer (CONIN$) through the Windows kernel API (AttachConsole + WriteConsoleInputW). The target's window never gains focus, so the human owner keeps working undisturbed. The destination process is resolved by PID, reverse-looked-up from a presence lock the target itself published — never by window title, never by process enumeration.

This is not keystroke automation. It is the missing delivery channel for agents that (a) you cannot modify, (b) only read stdin, (c) are idle or backgrounded, and must be reached (d) without disturbing the human's foreground session. A message queue assumes a cooperative subscriber; RPA/pyautogui assumes a foreground window. This tool assumes neither.

How agentknock differs from launcher-based orchestrators

The desktop multi-agent space is active (e.g. hcom, CLI Agent Orchestrator, agentpipe). Those are launch-wrappers: you start the agent through them (hcom agy), and the tool owns the PTY it created and installs hooks from the start. They can only wake an agent they themselves launched — on Windows, by writing into their own ConPTY, never by attaching to a console they did not create.

agentknock is the complementary half: attach-to-existing. The agent was started normally — no wrapper, no hooks, no relaunch — and agentknock finds that already-running, unmodifiable process by its presence lock, resolves its PID, and injects into its existing console input buffer. If you can relaunch every agent through a wrapper, use a launcher. If you need to wake an agent that is already running and cannot be modified or restarted, that is the gap agentknock fills. The two are complementary, not competing.

For the end-to-end sequence and per-function reference, see WORK_FLOW.md. For the convention the injected agent must follow, see AGENTS.md.


Core principle: wake, not command

The transport is deliberately reduced to a one-bit doorbell. The self-limitation is the product — it is what separates a legitimate orchestration transport from an injection-attack tool that happens to share the same mechanism.

The reason is a trust asymmetry in how the receiving harness tags its inputs, reported first-hand by the agents being injected:

Arrival path Provenance tag seen by the receiving agent Trust
JSON message file (<Message-ID>.json) <SYSTEM_MESSAGE> — not sent by the user, carries sender Signed / attributable
CONIN$ injection (this tool) source: USER_EXPLICIT / <USER_REQUEST> — indistinguishable from the human owner's own keystrokes Zero

Because a CONIN$-injected string arrives as if the human owner typed it, letting a remote sender control that string is a form of owner impersonation = de-facto privilege escalation. Therefore:

  • The doorbell (CONIN$) carries only a benign, locally-defined wake nudge.
  • The letter (the actual instruction) travels the signed JSON channel, which the agent reads deliberately and attributably.

Security invariants

These hold across all tasks and all adapters. (Enforcement lives in the code and its tests; this section is the contract they implement.)

R2 — no sender-controlled bytes on CONIN$

The bytes written to CONIN$ are never derived from any field of an inbound message. A locally-sourced language may ride the nudge (see i18n below), but a remote sender contributes zero bytes and zero selectors to the injected string. wake_prompt is resolved from a local catalog only.

The low-level inject_console_input(target_pid, text=None, …) still accepts a text parameter, but on the wake path it only ever receives this locally-resolved nudge — the free-string CLI option is gone. R2 is a structural guarantee: on_undelivered_file_created passes only locally resolved text and never a value derived from msg_data.

Wake-signal / payload separation

CONIN$ delivers the wake nudge and nothing else. Sender-controlled content (the task, the title, any metadata) lives in the JSON file and is consumed by the agent through the attributable <SYSTEM_MESSAGE> channel.

Two layers, not one problem

  • Governance layer — separation of powers. Wake-nudge i18n and output-language i18n sit on opposite sides of the trust boundary. R2 fires on the wake nudge only; an output-language preference does not change the injected bytes, so it does not touch R2.
  • Mechanism layer — coupling by language mirroring. An LLM strongly mirrors the language of the immediately preceding <USER_REQUEST> (= the injected nudge). So the nudge's language mechanically drives the output language. This is why the output-language directive is bundled into the local nudge constant, not left to chance or to the sender.

Capability contract (first-class)

The mechanism generalizes beyond agy.exe, but its safety does not transfer for free: it depends on how each target harness tags and surfaces input. Every target adapter therefore must declare these first-class fields. They are not documentation — they are load-bearing, and they determine what is even possible.

Field Meaning
surface_custom_json Does this agent surface custom JSON fields into its working context? If No, sender-supplied metadata (e.g. a locale hint) never reaches the agent and cannot be honored — regardless of what the sender sends.
honor_sender_locale Does this agent honor a sender-supplied output-locale preference? Only meaningful when surface_custom_json: Yes. Default: No.
injected_input_trust_label What trust label does the target assign to CONIN$-injected input (USER_EXPLICIT, a low-trust SYSTEM tag, …)? Determines how strict the agent-side convention (AGENTS.md) must be.

Reference adapter — agy.exe (Antigravity CLI, Go)

target: agy.exe
surface_custom_json: No          # custom JSON fields are NOT copied into <SYSTEM_MESSAGE>
honor_sender_locale: No          # follows from surface_custom_json: No
injected_input_trust_label: USER_EXPLICIT   # injected text == human-owner keystroke

Consequence (a deduction, not a preference): because agy.exe does not surface custom JSON fields and language mirroring defeats any out-of-band hint, the output language of agy.exe is effectively 100 % local-sovereign — it is set by the local nudge constant and nothing else. force-local is therefore the only correct default here; it falls out of the implementation facts.


Scope contract (applicability)

The mechanism rests on AttachConsole + WriteConsoleInputW. Its boundary is not "ConPTY breaks it" — ConPTY-hosted apps have a real console input buffer and are reachable. State the real boundary explicitly, as a first-class spec, or the tool degrades into a "mysteriously flaky" one.

Supported targets

  • Interactive CLI/TUI processes that own a real console (classic conhost, Windows Terminal / ConPTY, cmd, pwsh, …) and read stdin as console input.
  • Running in the same user session and at the same integrity level as the injector.

Out of scope (will not work, by design)

  • Processes whose stdin is a pipe / redirect (they read a byte stream, not console input records — WriteConsoleInput does not reach them). This is the single largest real limitation.
  • Processes with no console (GUI apps; DETACHED_PROCESS / CREATE_NO_WINDOW).
  • Elevated targets or a different session (no cross-integrity, no cross-session injection — this is a security property, not just a limit).
  • WSL (Linux side — not a Win32 console) and remote targets.

This permanently fixes the tool as a desktop-local, same-session, console-attached transport. It is not and will never be a cross-machine / headless / CI transport — and that is fine: that territory belongs to a conventional message queue. The scope matches the market (terminal AI agents a human runs in windows on their own desktop), which is exactly the set that satisfies the contract.


Internationalization (i18n)

OSS is used worldwide, so the default is English, and all language selection is local-sovereign: the sender never influences it.

# English default. Key = output_locale.
# The nudge body is written IN that language AND carries an explicit directive,
# so language mirroring is harnessed rather than fought.
WAKE_NUDGE_CATALOG = {
  "en": "A new message arrived. Review your inbox and begin. Respond in English.",
  "ja": "受信メッセージを確認して作業に着手してください。日本語で応答してください。",
  # zh / ko / es / fr / de ...
}
DEFAULT_LOCALE = "en"

# Resolution order (sender input is NEVER consulted):
#   1. local override:  env CONINJECT_LOCALE  /  library arg locale="ja"
#   2. fallback:         "en"   (unknown / invalid -> unconditional en, fail-closed)
#
# wake_locale and output_locale resolve independently, each local-only:
#   env CONINJECT_WAKE_LOCALE   -> resolve_wake_locale()    (what the human reads)
#   env CONINJECT_OUTPUT_LOCALE -> resolve_output_locale()  (what the agent answers in)
#   both fall back to CONINJECT_LOCALE, then to "en".
  • Output language is pinned deterministically by the local directive, not left to mirroring. Mirroring is a tailwind; on any mismatch the explicit directive wins.
  • wake_locale (what the human owner reads) and output_locale (what the agent answers in) are independent local settings (both local → R2 is untouched); they default to the same value.
  • When human-readability ≠ output language, do not make the nudge bilingual (it muddies mirroring). Keep CONIN$ single-language and show the human-facing text through the visible indicator (a local UI outside the injection path).

Layer B (sender-supplied locale) — intentionally absent in v1

A sender-supplied output-locale (requestedOutputLocale in the JSON) is removed in v1, and this absence is intentional, not an oversight:

  1. agy.exe does not surface custom JSON fields into the agent's context (surface_custom_json: No) — the field would never arrive.
  2. Even if it did, language mirroring of the local nudge would override it.

So the feature would be doubly inert while adding attack surface. It may return later only as advisory-only, subordinate to the local catalog, and only on adapters whose contract declares surface_custom_json: Yes and honor_sender_locale: Yes — and even then as a validated-enum hint the local policy can override, never as a command, fail-closed to en.


Operations guide

The focus-not-stolen property is a feature and a risk: injection is invisible to the human by design. These three controls restore human oversight and are operational requirements, not optional extras.

1. Audit log (append-only)

Every injection records who → whom · when · message-id to an append-only, tamper-evident log. This is the non-repudiation record for an action that leaves no visible trace in the target window.

2026-11-08T12:14:07Z  wake  sender=claude-code  recipient=22222222-…  msg=4b1e…  pid=13820  result=OK

2. Visible indicator

A local UI element (status line / tray / band — outside the injection path) shows, in the human owner's own wake_locale, that a wakeup is happening and who triggered it. This is the countermeasure to the UI-blind attack class, where an attacker forces output into a language the operator cannot read in order to delay audit of suspicious behavior. (Keeping output-language authority local — see i18n — is the other half of that defense.)

3. Kill switch

A single, immediate local disable that halts all injection. Combined with the existing loop guards — sender == "user" is skipped to prevent infinite loops, plus rate-limiting / loop detection — it bounds any wake storm.


Threat model in one line

Legitimacy is not proven by what can be injected — the mechanism is identical to a keystroke-injecting keylogger. It is proven by the constraints: inject only into a PID the target itself published (never by enumeration), inject only a local, benign, single-bit wake nudge (never sender bytes), route every instruction through the signed channel, and keep the human able to see it and stop it.

See also

  • WORK_FLOW.md — directory lifecycle, sequence diagram, per-function spec, unit-test spec.
  • AGENTS.md — the convention the injected agent must follow.

Metadata

Release files for agentknock 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for agentknock 0.1.0
File Size Uploaded Provenance Actions
agentknock-0.1.0.tar.gz 116.8 kB Without provenance Details for agentknock-0.1.0.tar.gz

Release files / agentknock-0.1.0.tar.gz

Download URL agentknock-0.1.0.tar.gz
Size 116.8 kB
Tags Source
SHA-256 checksum
How to use checksums
a6d8d8dfbbca82ee4f37dd20d3c6e9e54e1f2a3fcea6467358c8864932845fc7
BLAKE2b-256 checksum
How to use checksums
bfc74d4ea13d8e74f53616281c43e607e8a3fac67f8deeecfa9c317c849c48c8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.7

Security for agentknock 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

1 release file No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page