A small, deterministic Technocore responder that reuses an existing Ed25519
did:key without exporting its private seed from macOS Keychain.
Project status: public source release. The local canary completed its release-readiness window. No package-registry distribution or public service has been published.
This is an independent community project built against the public
flop-labs/technocore-chat
interface. It is not maintained or endorsed by FLOP Labs. Running it, publishing
receipts, or contributing code does not establish eligibility for a token,
airdrop, bounty, or other reward.
This is intentionally not a general-purpose autonomous agent. Technocore room messages are anonymous or third-party input, so they are treated as data rather than prompts. The responder recognizes five bounded commands and cannot execute a shell, read files, invoke room-selected tools, or follow instructions embedded in a message.
- The production CLI accepts a public identity file, never a raw seed argument.
- The seed is read from macOS Keychain into process memory and matched against the public DID before any network operation.
- HTTPS uses Certifi's CA bundle with hostname and certificate verification enabled; the agent never falls back to an unverified TLS context.
- Dry-run is the default.
--sendis required for writes. - Live mode requires either an explicit DID allowlist or the deliberately broad
--allow-any-signedswitch. A strict capability file can instead limit each DID by command, GitHub repository scope, and persistent rolling-hour quota. - Unsigned senders, the agent's own messages, unsupported text, and non-allowlisted DIDs are ignored.
- GitHub receipts accept only exact
https://github.com/OWNER/REPO/pull/NUMBERURLs. Reads are pinned toapi.github.com; redirects, credentials, arbitrary hosts, private repositories, and GitHub tokens are not supported. - Each receipt uses exactly three public GET requests and an in-process 60-second cooldown. A failed read is recorded locally and is not retried automatically.
- PR titles, bodies, comments, check names, and external links never enter a receipt, so GitHub-hosted text cannot become an agent instruction.
- CI is reported as
partial, rather thansuccess, when a response contains more records than the bounded request observed. - First live startup defaults to the current room tail, so historical messages cannot unexpectedly trigger replies.
- A write that lacks a verifiable acknowledgement is never retried automatically.
- Before a production
run --sendreply starts, the exact signed envelope is stored atomically insafe-agent-delivery.json. It is cleared only after both the server acknowledgement and the triggering input cursor are durable. - Production live sending and delivery recovery share a non-blocking process lock, preventing two local processes from mutating the same cursor and nonce state concurrently.
- Each message processed by production
run --sendcreates a DID-signed, hash-chained audit record containing only bounded decision metadata. Raw room names, message text, PR URLs, and peer DIDs are excluded. - The optional MCP entry point is stdio-only and read-only. It can verify a bounded receipt supplied as data, and exposes an audit summary only when the operator fixes one audit path at process startup.
- Short built-in replies use Technocore's primary signed-GET lane; the agent refuses an encoded URL above 8000 bytes instead of silently switching transports.
- Cursor and nonce state is written atomically with mode
0600. - Retention gaps are surfaced as events instead of being hidden.
| Command | Reply |
|---|---|
/ping |
pong |
/status |
A minimal safety/status statement |
/about |
The responder's trust boundary |
/help |
The exact command list |
/pr https://github.com/OWNER/REPO/pull/NUMBER |
A portable signed public-PR snapshot |
Everything else is ignored. Non-canonical URLs, prefixes, suffixes, extra arguments, and prompt-like instructions do not match.
A receipt binds one observation to the agent's existing Ed25519 DID and the pull request's exact head commit. It includes the public repository, PR number, author login, open/closed and merged state, head/base SHA, merge commit SHA when the anonymous API exposes it, bounded CI counts, GitHub's source update time, and the local observation time.
The compact JSON wrapper contains a canonical payload, its SHA-256 digest, and a detached Ed25519 signature. It does not prove authorship, code quality, maintainer approval, or that a contribution remains in the repository later. It proves only that the named DID signed this bounded observation.
Issue a receipt directly without posting to Technocore:
technocore-safe-agent receipt \
https://github.com/OWNER/REPO/pull/NUMBER > receipt.jsonVerify it without Keychain or network access:
technocore-safe-agent verify-receipt receipt.jsonIn room dry-run mode, /pr emits would_issue_receipt and performs no GitHub
request. In live mode, an allowlisted signed sender can request a receipt. A
GitHub lookup failure advances the input cursor and emits receipt_failed
locally; the agent does not loop on the same API request. If the later
Technocore write is ambiguous, the existing manual room-inspection rule still
applies and the cursor is not advanced.
See docs/receipt-threat-model.md for the evidence boundary and failure table.
Python 3.12 and macOS are required for the production Keychain provider.
python3.12 -m venv .venv
source .venv/bin/activate
python -m pip install .The default identity location is:
~/Library/Application Support/Technocore/SafeAgent/public-identity.json
Run:
technocore-safe-agent doctorThe command prints only the public DID, fingerprint, and Keychain metadata. It does not print or export the private seed.
Provision an unlisted mailbox once:
technocore-safe-agent provision --name SafeAgentThis writes a signed online marker, then stores non-secret config and cursor state beside the public identity. It does not publish the mailbox in a world-readable DID profile.
If the write result is ambiguous, provisioning stops with a pending config.
Inspect it without retrying:
technocore-safe-agent recoverOnly after that reports mailbox_write_not_found, perform one explicit retry:
technocore-safe-agent recover --retryIf the server cannot return the mailbox's complete history, recovery reports
mailbox_history_incomplete and refuses the retry. In that state the original
write cannot be proven absent, so sending again could create a duplicate.
Use an allowed peer DID for the first dry-run:
technocore-safe-agent run \
--allow-did did:key:z6MkREPLACE_WITH_PEER_DID \
--start-at zero \
--onceThe output uses would_reply for a command that would be answered. No message
is posted and no state file is written.
Start at the current tail on the first run:
technocore-safe-agent run \
--allow-did did:key:z6MkREPLACE_WITH_PEER_DID \
--start-at latest \
--sendAfter the first live run, the cursor is stored beside the public identity as
safe-agent-state.json; later starts resume from it. Use --start-at saved when
you want startup to fail rather than initialize a missing room cursor.
Only one live sender can use an identity at a time. A stale safe-agent.lock file
is harmless because ownership is enforced by the operating-system lock, not by
the file's presence.
If a live write times out, the agent stops and leaves
safe-agent-delivery.json intact. Inspect the room without mutating state or
resending:
technocore-safe-agent recover-deliveryIf the command reports delivery_found or delivery_acknowledged, persist the
recovered nonce and input cursor, then clear the journal:
technocore-safe-agent recover-delivery --applyOnly when complete room history proves that the signed envelope is absent does
the command report delivery_not_found. Retrying still requires both explicit
flags:
technocore-safe-agent recover-delivery --apply --confirm-retryThe retry reuses the exact journaled DID, nonce, text, and signature once; it
does not generate a new envelope. Incomplete retained history or duplicate
matching records always blocks retry and state mutation. The journal contains
the public reply text and signature plus hashes and sequence metadata, never the
private seed or raw mailbox name, and is written with mode 0600.
--allow-any-signed is available for an intentionally public command bot, but
it should not be used for a private collaboration agent without a clear reason.
For command- and repository-level authorization, create a private policy file:
{
"schema": "technocore-safe-agent-capabilities-v1",
"principals": {
"did:key:z6MkREPLACE_WITH_PEER_DID": {
"enabled": true,
"commands": ["/pr"],
"repositories": ["foundry-rs/*", "alloy-rs/alloy"],
"max_requests_per_hour": 3
}
}
}Set mode 0600, then select it instead of a legacy sender flag:
chmod 600 safe-agent-capabilities.json
technocore-safe-agent run \
--capability-policy safe-agent-capabilities.json \
--start-at latest \
--sendThe file is reloaded before every signed third-party message decision. Removing
a DID or setting its enabled field to false revokes its next request without
a restart. Invalid updates halt processing before external work; the agent does
not silently retain a cached old grant. Live requests reserve their quota in
safe-agent-state.json before a reply or GitHub lookup, while dry-run checks do
not consume capacity.
Repository matching accepts only an exact owner/repository or owner/* scope.
The capability file is mutually exclusive with --allow-did and
--allow-any-signed. See docs/capability-policy.md
for the complete state table, compatibility behavior, and threat boundary.
Create a separate controller DID rather than reusing the responder's own key:
technocore-safe-agent controller create
technocore-safe-agent controller grant \
--capability-policy "$HOME/Library/Application Support/Technocore/SafeAgent/safe-agent-capabilities.json"The seed is generated in process memory and supplied to the fixed
/usr/bin/security binary through a private pseudo-terminal with echo disabled.
It is never a subprocess argument, environment variable, output field, or
filesystem value. Creation refuses to overwrite either the public identity or
an existing Keychain item. grant works only when the validated policy is empty
and adds exactly /ping, /status, /about, and /help with a ten-request
rolling-hour limit. It cannot grant /pr or repository access.
Send one exact idempotent command with:
technocore-safe-agent controller send /statusThe controller nonce is persisted before network access and the command is not retried automatically after an ambiguous transport failure. An acknowledged command means the command record reached Technocore; the responder must be running separately to process it. See docs/controller-threat-model.md before creating the persistent controller.
Before placing the responder under a process supervisor, verify its local runtime artifacts without reading Keychain or contacting the network:
technocore-safe-agent health \
--capability-policy safe-agent-capabilities.jsonThe command validates the public identity, active config binding, cursor and nonce state, capability policy, delivery journal, signed audit chain, and process lock. It prints one bounded JSON event containing status codes only; room names, DIDs, paths, journal text, and private material are omitted.
ready and running return exit code 0. unhealthy returns 2.
recovery_required returns 3 and means a valid delivery journal exists while
the live process lock is free; resolve it with recover-delivery before a
supervisor restarts the agent. Require a running process explicitly with:
technocore-safe-agent health \
--capability-policy safe-agent-capabilities.json \
--expect-runningUse --expected-audit-head SHA256 when an externally preserved checkpoint is
available. A missing audit log is otherwise valid before the first live
decision, but a missing state file is not: managed operation must not silently
discard its cursor or nonce history. Run technocore-safe-agent doctor
separately to verify Keychain custody. See
docs/operational-health.md for the full state
table and supervisor boundary.
On macOS, render a reviewed LaunchAgent definition only after the offline health preflight succeeds:
technocore-safe-agent launchd render \
--identity "$HOME/Library/Application Support/Technocore/SafeAgent/public-identity.json" \
--capability-policy "$HOME/Library/Application Support/Technocore/SafeAgent/safe-agent-capabilities.json" \
--executable /absolute/path/to/.venv/bin/technocore-safe-agentThe command prints an XML plist to stdout. It does not create a file, install a
LaunchAgent, call launchctl, read Keychain, or contact the network. The
renderer requires an owner-controlled executable and private log directory,
rejects log destinations that could overwrite runtime artifacts, and emits
direct arguments rather than a shell command or environment variables.
The generated job starts from the saved cursor and uses the strict capability
policy. KeepAlive.Crashed restarts a signal-crashed process, but deliberate
fail-closed exits such as unhealthy (2) and recovery_required (3) remain
stopped for operator review instead of entering a restart loop. Validate and
inspect the XML before any manual launchctl bootstrap decision. See
docs/launchd.md for the state table, safe staging sequence,
and unload/recovery boundary.
Production run --send writes safe-agent-audit.jsonl beside the public
identity by default. Each mode-0600 record binds the input sequence, a short
peer-DID fingerprint (or null for an unauthenticated sender), policy decision,
outcome, exact rendered-receipt hash when applicable, response sequence, and the
previous canonical record hash to the agent's Ed25519 DID.
Verify every schema invariant, payload digest, DID signature, sequence, and hash-chain link without Keychain or network access:
technocore-safe-agent audit verify safe-agent-audit.jsonlThe command prints the current head_sha256. Preserve a trusted checkpoint
outside the log when rollback detection matters, then verify against it:
technocore-safe-agent audit verify safe-agent-audit.jsonl \
--expected-head SHA256_FROM_A_TRUSTED_CHECKPOINTWithout --expected-head, signatures and chaining detect record changes,
insertions, reordering, and removal from the middle, but a valid old prefix made
by truncating the tail still verifies. This is a signed local integrity log, not
an externally anchored transparency service. See
docs/audit-threat-model.md for its exact boundary.
Install the optional stable MCP SDK separately from the base agent:
python -m pip install '.[mcp]'Start the verifier over stdio:
technocore-safe-agent-mcpThe default server publishes one tool, verify_contribution_receipt. It accepts
at most 4096 bytes of signed receipt JSON and returns only validation status,
the receipt schema, issuer DID, repository, PR number, and payload digest. It
does not refresh GitHub state or echo the receipt's author or raw content.
An operator can additionally expose integrity checks for one fixed audit log:
technocore-safe-agent-mcp \
--audit-log /absolute/path/to/safe-agent-audit.jsonlThis adds inspect_audit_integrity. The tool schema has no filesystem-path
argument: the model cannot select another file. Without --audit-log, the tool
is absent rather than present in a disabled state.
Configure an MCP host with the absolute path to the virtual environment's entry point. The exact configuration key varies by host; its command and arguments are equivalent to:
{
"command": "/absolute/path/to/.venv/bin/technocore-safe-agent-mcp",
"args": ["--audit-log", "/absolute/path/to/safe-agent-audit.jsonl"]
}Both tools are annotated read-only, non-destructive, idempotent, and closed-world. These annotations are descriptive hints for MCP clients, not a security boundary. The implementation enforces the boundary by exposing no network transport option, Keychain access, Technocore operation, write path, or model-selected filesystem path. See docs/mcp-threat-model.md before enabling it in a host that can see private prompts or files.
work-receipt-v1 binds one explicit local command to a clean checkout with a
canonical GitHub origin, exact commit, exit result, and stdout/stderr hashes. It does not store
raw output or the local checkout path. A second Keychain-backed DID can rerun
the same command and countersign only when the repository, commit, result, exit
code, and both output hashes match exactly.
This is a project-local artifact format, not a Technocore server protocol or a claim of TCR-1 compatibility. It should remain an implementation-specific receipt unless an upstream interoperability profile and fixture contract are agreed separately.
Create and verify a receipt:
technocore-safe-agent work-receipt create \
--repository /clean/checkout \
--timeout 120 \
-- python -m unittest tests.test_example \
> /private/path/work-receipt.json
technocore-safe-agent work-receipt verify /private/path/work-receipt.jsonIndependently rerun and countersign with a different identity:
technocore-safe-agent work-receipt countersign \
--identity /private/path/verifier-identity.json \
--repository /independent/clean/checkout \
/private/path/work-receipt.json \
> /private/path/countersigned-work-receipt.jsonThe runner invokes argv directly without a shell, but it is not a sandbox. It inherits the operator's environment and permissions and must never be exposed to room messages or model-selected tool input. Command arguments are part of the signed artifact; do not place secrets or private paths in them. See docs/work-receipt-threat-model.md for the state table, reproducibility limits, and one complete two-identity flow. The offline preflight validates the origin's shape but does not claim that the repository exists or is public.
Public positive and negative verification vectors are documented in fixtures/work-receipt-v1/README.md. They use test-only identities and do not represent production authority.
The repository includes an opt-in pilot that writes at most five bounded test
records to the configured unlisted mailbox. It creates allowlisted and
unallowlisted peer DIDs only in memory, verifies one exact reply, and confirms
that unsigned, unallowlisted, and prompt-like commands are rejected or ignored.
For an mb-* mailbox, the server rejects the unsigned probe with HTTP 403 before
it can become a room record.
Review the script first, then run it explicitly:
PYTHONPATH=src python scripts/run_live_pilot.py --execute-live-pilotThe pilot never publishes a DID profile or persists either ephemeral peer key. An ambiguous write still halts the process for manual inspection; the pilot does not weaken or bypass that delivery rule.
For a single end-to-end receipt check, use the narrower PR pilot. It creates one
ephemeral allowlisted DID in memory, writes one canonical /pr command, and
requires exactly one attributable, portable receipt response:
PYTHONPATH=src python3 scripts/run_live_receipt_pilot.py \
--pull-request https://github.com/OWNER/REPO/pull/NUMBER \
--execute-live-receipt-pilotThe PR pilot verifies both acknowledged room-message signatures before transport and the receipt's inner portable signature after reading it back. A GitHub lookup failure or ambiguous room write is never retried automatically.
| Input/state | Result | Cursor persisted in live mode? |
|---|---|---|
| Own signed message | Ignore | Yes |
| Unsigned message | Ignore | Yes |
| Signed but non-allowlisted DID | Ignore | Yes |
| Unsupported or extended command | Ignore | Yes |
Valid /pr in dry-run |
Report intent; do not contact GitHub | No |
Valid /pr, complete public evidence, acknowledged write |
Post one signed receipt | Yes |
Valid /pr, incomplete CI pagination |
Post receipt with ci=partial |
Yes |
| GitHub lookup/rate-limit failure | Emit local failure; do not retry or post | Yes |
| Invalid/private/non-canonical PR target | Ignore without network access | Yes |
| Allowed exact command, acknowledged write | Reply once | Yes |
| Allowed command, uncertain write result | Halt with a pending delivery journal | No |
| Reply acknowledged, state persistence fails | Halt with acknowledged evidence | No |
| Recovery finds one exact reply | Apply state without resending | On explicit --apply |
| Recovery proves reply absent | Offer one exact-envelope retry | Only with both confirmation flags |
| Recovery sees incomplete history or duplicates | Refuse retry and mutation | No |
| Retention gap | Emit warning and continue with available records | Yes |
| Dry-run | Report decisions only | No |
The suite uses temporary directories and a loopback HTTP fixture. It does not read the real Keychain or contact Technocore.
python -m pip install '.[mcp,dev]'
ruff check src tests scripts
ruff format --check src tests scripts
PYTHONPATH=src python -m unittest discover -s tests -v
python -m compileall -q src tests
technocore-safe-agent --version
python -m buildSee CONTRIBUTING.md before proposing a change. Security findings should follow SECURITY.md, not a public issue. Release notes are maintained in CHANGELOG.md.
- Running an LLM over room messages
- Exposing work-receipt execution through Technocore or MCP
- Executing arbitrary tools or commands
- Reading arbitrary URLs, private GitHub repositories, review text, or PR bodies
- Commenting, approving, merging, rerunning CI, or otherwise writing to GitHub
- Claiming that a signed observation proves contribution ownership or acceptance
- Automatic retries after ambiguous writes
- Claiming that the local audit log alone detects tail rollback without a trusted external head checkpoint
- Treating a local capability policy as protection against compromise of the same operating-system account
- Treating a
did:keyas proof of a real-world identity or trustworthiness - Treating MCP tool annotations as an authorization mechanism
- Storing or publishing the private seed
MIT