diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..87d9f66 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,57 @@ +# Copilot instructions for SourceOS AgentTerm + +AgentTerm is the Matrix-first terminal ChatOps console for SourceOS. Do not implement it as a generic chatbot wrapper. + +Read these before making changes: + +- `AGENTS.md` +- `docs/architecture/sourceos-control-surface.md` +- `docs/integration/agent-registry-boundary.md` +- `docs/integration/holmes-boundary.md` + +## Architecture guardrails + +- Matrix is the canonical transport. Slack and Discord integrations are bridges. +- AgentTerm owns terminal UX, normalized local events, slash-command parsing, and adapter boundaries. +- Agent Registry owns agent specs, identities, sessions, memories, tool grants, revocation, and runtime authority. Every non-human participant must resolve through Agent Registry before enablement. +- Sociosphere owns workspace manifest, lock, topology, registry, validation lanes, and release-readiness orchestration. +- Prophet Workspace owns Professional Workrooms and workspace product semantics. +- Slash Topics owns topic scopes and policy membranes. +- Memory Mesh owns governed recall/writeback and context packs. +- New Hope owns message/thread/claim/citation semantic runtime semantics. +- Holmes must not be redefined here. AgentTerm may only request, display, correlate, and audit Holmes-owned work. +- Sherlock Search owns search packets and retrieval-evidence surfaces. Legacy Sherlock OSINT remains disabled by default and policy-gated. +- MeshRush owns graph-view/diffusion/crystallization semantics. +- cloudshell-fog owns shell placement, OIDC, TTL, PTY attach, and audit. +- AgentPlane owns validation, placement, runs, replay, and evidence artifacts. +- Policy Fabric owns command admission and sensitive context-release decisions. +- Side-effecting operations require explicit approval paths. +- Avoid adding SDK-specific logic directly to `cli.py`. Keep adapters narrow and testable. +- Local config may reference participants, but it is not runtime authority. + +## Validation commands + +```bash +python -m pip install -e '.[dev]' +ruff check . +pytest +``` + +## Current priority order + +1. Matrix adapter with room/event/E2EE posture preservation. +2. Agent Registry adapter for participant identity, grants, sessions, and revocation. +3. Policy Fabric admission stub for side-effecting commands and sensitive context release. +4. Sociosphere workspace state adapter. +5. Prophet Workspace workroom binding and receipt adapter. +6. Slash Topics scope/membrane adapter. +7. Memory Mesh recall/writeback/context-pack adapter. +8. New Hope semantic event adapter. +9. Sherlock Search packet validation/hydration adapter. +10. Holmes request/status/artifact correlation adapter, respecting `docs/integration/holmes-boundary.md`. +11. MeshRush graph operation adapter. +12. cloudshell-fog session lifecycle adapter. +13. AgentPlane validate/place/run/evidence adapter. +14. GitHub/CI bridge adapters. +15. Hermes, Codex, Claude Code, and OpenCLAW participant adapters gated by Agent Registry. +16. Textual TUI for rooms, threads, agents, grants, workrooms, topics, memory, semantic objects, investigations, graphs, approvals, and evidence. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..dcf840e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,29 @@ +name: CI + +on: + pull_request: + push: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install package + run: | + python -m pip install --upgrade pip + python -m pip install -e '.[dev]' + + - name: Lint + run: ruff check . + + - name: Test + run: pytest diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3a2808b --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# Python +__pycache__/ +*.py[cod] +.pytest_cache/ +.ruff_cache/ +.venv/ +venv/ +build/ +dist/ +*.egg-info/ + +# AgentTerm local state +.agent-term/ +*.sqlite3 +*.sqlite3-shm +*.sqlite3-wal + +# OS/editor +.DS_Store +.idea/ +.vscode/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..e95ee35 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,87 @@ +# Agent instructions for AgentTerm + +AgentTerm is the terminal-native Matrix-first ChatOps console for SourceOS. It is not a single-agent chat wrapper. + +## Mandatory architecture boundaries + +- Matrix is the canonical ChatOps transport. Slack and Discord are bridge targets, not the source of truth. +- AgentTerm is the operator surface and normalized event log. It does not own agent identity authority, workspace topology, workroom semantics, topic scopes, memory runtime, semantic runtime, investigation runtime, search-packet schemas, graph runtime, shell placement, bundle execution, policy release, or CI execution. +- Agent Registry owns agent specs, identities, sessions, memories, tool grants, revocation, and runtime authority. Every non-human AgentTerm participant must resolve through Agent Registry before enablement. +- Sociosphere owns canonical workspace manifests, locks, topology, registry metadata, validation lanes, and release-readiness orchestration. +- Prophet Workspace owns workspace product semantics, Professional Workrooms, workspace app surfaces, policy-aware UX, audit, and receipts. +- Slash Topics owns governed, signed, replayable topic scopes and policy membranes for search/knowledge operations. +- Memory Mesh owns governed recall, writeback, context packs, memoryd runtime, LiteLLM hooks, and OpenCLAW memory tools. +- New Hope owns semantic runtime semantics for messages, threads, claims, citations, entities, lenses, receptors, membranes, and moderation events. +- Holmes owns language intelligence: casefiles, retrieval, semantic graphs, synthesis, guardrails, evals, and investigative discovery. AgentTerm may request/status/correlate Holmes work but must not redefine Holmes. +- Sherlock Search owns discovery/search-packet and retrieval-evidence surfaces. Legacy Sherlock username/social-network lookup is high-friction and policy-gated only. +- MeshRush owns graph-native autonomous-agent runtime semantics over graph views, diffusion, crystallization, traces, and graph evidence. +- cloudshell-fog owns governed shell/session placement, OIDC, TTL, PTY attach, and audit semantics. +- AgentPlane owns bundle validation, executor placement, runs, evidence artifacts, and replay. +- Policy Fabric owns policy decision/evidence surfaces for side-effecting operations and sensitive context release. + +## Required invariants + +1. Every non-human participant requires Agent Registry identity resolution before enablement. +2. Tool use, capability grants, runtime sessions, memory authority, and revocation checks must resolve through Agent Registry. +3. Side-effecting commands require an approval path. +4. Sensitive context release requires Policy Fabric admission. +5. Matrix room IDs, event IDs, membership changes, redactions, bridge metadata, and E2EE posture must be preserved when available. +6. Workroom, topic, memory, semantic-thread, investigation, search-packet, graph, execution, and shell events must remain explicit and auditable. +7. Slash Topics scopes should constrain Memory Mesh recall, Sherlock Search packets, Holmes investigations, New Hope semantic routing, and MeshRush graph view selection. +8. Memory Mesh must not be treated as hidden prompt history; recall/writeback requires explicit event metadata and policy posture. +9. New Hope is not handled by Sherlock or Holmes; it is the semantic commons runtime underneath message/thread/claim/citation operations. +10. Holmes investigates; Sherlock retrieves; New Hope normalizes semantic objects; Memory Mesh supplies governed context; Slash Topics scopes the operation. +11. AgentPlane evidence artifacts must remain visible in AgentTerm events. +12. cloudshell-fog shell attach must not bypass OIDC, placement, TTL, or audit semantics. +13. Legacy Sherlock OSINT must never become an ambient default tool. +14. Adapter code must be behind narrow contracts; do not hardwire vendor SDKs into the terminal shell. +15. Local event-log data must not be committed. + +## Development commands + +```bash +python -m venv .venv +source .venv/bin/activate +python -m pip install -e '.[dev]' +ruff check . +pytest +``` + +## Useful smoke commands + +```bash +agent-term init +agent-term planes list +agent-term planes show agent-registry +agent-term planes show new-hope +agent-term record agent-registry agent_identity '!agent-registry' 'Resolve agent.codex before dispatch' +agent-term record prophet-workspace workroom '!prophet-workspace' 'Bind PI demo workroom' --metadata-json '{"workroom":"pi-demo"}' +agent-term record slash-topics topic_scope '!slash-topics' 'Select /professional-intelligence topic scope' +agent-term record memory-mesh memory_recall '!memory-mesh' 'Recall workroom context' --requires-approval +agent-term record new-hope semantic_thread '!new-hope' 'Normalize Matrix thread into semantic commons objects' +agent-term record holmes investigation '!holmes' 'Investigate evidence gap' --requires-approval +agent-term sherlock-packet '!sourceos-intel' 'find workroom context for AgentTerm Sherlock integration' --workroom agent-term --topic professional-intelligence +agent-term record meshrush graph_view '!meshrush' 'Enter professional intelligence graph view' --requires-approval +agent-term request-shell '!sourceos-build' default --thread-id demo-shell +agent-term tail +``` + +## Preferred implementation order + +1. Keep the local event model and SourceOS plane registry stable. +2. Add Matrix adapter read/write with E2EE posture surfaced. +3. Add Agent Registry participant identity, grant, session, and revocation adapter. +4. Add Policy Fabric command admission stub before side-effecting adapter execution. +5. Add Sociosphere workspace manifest/topology adapter. +6. Add Prophet Workspace workroom binding and receipt adapter. +7. Add Slash Topics topic-pack and membrane adapter. +8. Add Memory Mesh recall/writeback/context-pack adapter. +9. Add New Hope semantic-thread/message/claim/citation adapter. +10. Add Sherlock Search packet validation/hydration flow. +11. Add Holmes request/status/artifact correlation flow without redefining Holmes. +12. Add MeshRush graph-view/diffusion/crystallization flow. +13. Add cloudshell-fog session request/attach flow. +14. Add AgentPlane validate/place/run/evidence flow. +15. Add GitHub and CI bridge events. +16. Add Hermes, Codex, Claude Code, and OpenCLAW participant adapters gated by Agent Registry. +17. Add richer Textual TUI views for rooms, threads, agents, grants, workrooms, topics, memory, semantic objects, investigations, graphs, approvals, and evidence. diff --git a/README.md b/README.md index 49aebbd..8f634d8 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,140 @@ -# agent-term -AgentTerm: terminal-native ChatOps console for multi-agent operator workflows across SourceOS, AgentPlane, GitHub, CI, MCP, and LLM agents. +# AgentTerm + +AgentTerm is a terminal-native ChatOps console for coordinating human operators, Matrix rooms, registered agents, GitHub bots, CI systems, MCP tools, Agent Registry identities, Sociosphere workspace materialization, Prophet Workspace workrooms, Slash Topics scopes, Memory Mesh context, New Hope semantic threads, Holmes investigations, Sherlock search packets, MeshRush graph operations, cloudshell-fog sessions, AgentPlane runs, Policy Fabric decisions, and local SourceOS services from one channel/thread workspace. + +The design target is not another single-agent CLI. AgentTerm is the Slack-term class interface for agent operations: rooms, channels, threads, slash commands, approvals, event logs, adapters, and terminal-first operator flow. For SourceOS, Matrix is the canonical network ChatOps substrate. Slack and Discord should be treated as bridge targets, not the source of truth. + +## What this repo seeds + +- A Python CLI package named `agent-term`. +- A local SQLite event log for durable operator history. +- A minimal interactive shell for immediate use. +- A first-class SourceOS plane registry for Matrix, Agent Registry, Sociosphere, Prophet Workspace, Slash Topics, Memory Mesh, New Hope, Holmes, Sherlock Search, legacy Sherlock, MeshRush, cloudshell-fog, AgentPlane, Policy Fabric, GitHub, CI, MCP, Hermes, Codex, Claude Code, and OpenCLAW. +- Adapter contracts for Matrix, SourceOS planes, and process-backed participants. +- Governance-preserving command shapes for workroom, topic, memory, semantic-thread, investigation, search-packet, graph-view, and shell-session requests. +- Agent identity posture: local config may reference agents, but Agent Registry is the authority for specs, identities, sessions, memories, tool grants, revocation, and runtime authority. +- Configuration examples, tests, CI, and operating-model docs for building AgentTerm into the SourceOS operator console. + +## Core concept + +```text +┌──────────────────────────┬─────────────────────────────────────────────┐ +│ Matrix Rooms / Channels │ Thread / Conversation │ +│ !agent-registry │ @agent-term: resolve @codex before dispatch │ +│ !prophet-workspace │ @operator: /workroom pi-demo │ +│ !slash-topics │ @operator: /topic professional-intelligence │ +│ !memory-mesh │ @operator: /memory recall workroom context │ +│ !new-hope │ @agent-term: semantic thread normalized │ +│ !holmes │ @operator: /holmes investigate evidence gap │ +│ !sherlock-search │ @operator: /sherlock scoped search packet │ +│ !meshrush │ @operator: /meshrush enter graph view │ +│ !agentplane │ @claude-code: proposes patch plan │ +│ !policyfabric │ @github: PR #42 checks failing │ +│ !cloudshell-fog │ @operator: /request-shell default │ +├──────────────────────────┴─────────────────────────────────────────────┤ +│ /workroom /topic /memory /newhope /holmes /sherlock /meshrush │ +└────────────────────────────────────────────────────────────────────────┘ +``` + +AgentTerm treats every meaningful action as an event: + +- human chat messages +- slash commands +- registered-agent identity, grant, session, and revocation events +- agent replies +- Matrix room events, redactions, membership changes, and bridge events +- Sociosphere workspace manifest, lock, topology, and validation events +- Prophet Workspace workroom, audit, policy-aware UX, and receipt events +- Slash Topics topic-scope, policy-membrane, and receipt events +- Memory Mesh recall, writeback, and context-pack events +- New Hope message, thread, claim, citation, receptor, membrane, and moderation events +- Holmes request/status/artifact correlation events, without redefining Holmes behavior +- Sherlock Search search-packet and context-hydration events +- MeshRush graph-view, diffusion, crystallization, trace, and graph-evidence events +- GitHub issue and PR updates +- CI status transitions +- MCP tool calls +- Policy Fabric decisions +- AgentPlane validation, placement, run, replay, and evidence artifacts +- cloudshell-fog session and shell-attach events +- handoffs between agents + +The event log is the control plane. The terminal UI is only the operator surface. + +## Why Matrix first + +Matrix gives us a federated, open ChatOps substrate instead of binding the system to a vendor workspace. AgentTerm should be able to operate inside Matrix rooms, bridge to Slack/Discord where required, and preserve enough event metadata to audit agent actions. + +Initial Matrix requirements: + +- map AgentTerm channels to Matrix room IDs or aliases +- preserve Matrix event IDs for auditability +- support threaded replies where homeserver/client support exists +- model redactions as first-class governance events +- expose membership changes as security-relevant events +- support encrypted-room posture checks before agents receive sensitive context +- keep bridge metadata when Matrix rooms bridge to Slack, Discord, GitHub, or CI systems + +## Authority split + +AgentTerm should not collapse the SourceOS stack into one generic search agent. + +| Plane | Authority | +| --- | --- | +| Agent Registry | Agent specs, identities, sessions, memories, tool grants, revocation, and runtime authority for every non-human participant | +| Sociosphere | Meta-workspace controller, canonical workspace manifest, lock, topology, governance registry, validation lanes | +| Prophet Workspace | Workspace product semantics, Professional Workrooms, mail/calendar/drive/docs/chat/meeting surfaces, policy-aware UX | +| Slash Topics | Governed signed topic scopes, policy membranes, search/knowledge scoping, replayable receipts | +| Memory Mesh | Governed recall, writeback, context packs, memoryd runtime, LiteLLM/OpenCLAW memory integrations | +| New Hope | Semantic runtime for messages, threads, claims, citations, entities, lenses, receptors, membranes, and moderation events | +| Holmes | External language-intelligence fabric; AgentTerm may request, display, correlate, and audit Holmes-owned work, but must not define it | +| Sherlock Search | Discovery/search-packet surface and retrieval evidence engine | +| Legacy Sherlock | Explicitly authorized, policy-gated username/social-network OSINT only | +| MeshRush | Graph-native autonomous-agent runtime over typed hypergraph world-model views | +| cloudshell-fog | Governed fog/cloud shell session placement, OIDC, TTL, PTY attach, and audit | +| AgentPlane | Validated bundle execution, executor placement, run/replay artifacts, and evidence | +| Policy Fabric | Policy decision/evidence authority for side effects and sensitive context release | + +New Hope is not “handled by” Sherlock or Holmes. Holmes investigates, Sherlock retrieves, and New Hope normalizes the semantic commons objects they operate over. + +Every non-human AgentTerm participant must resolve through Agent Registry before enablement. This includes Hermes, Codex, Claude Code, OpenCLAW, Matrix bots, GitHub bots acting as agents, CI bots acting as agents, MCP-backed tools, local process agents, and future SourceOS agents. Local config is a desired binding, not authority. + +## MVP commands + +After cloning locally: + +```bash +python -m venv .venv +source .venv/bin/activate +pip install -e '.[dev]' +agent-term init +agent-term planes list +agent-term planes show agent-registry +agent-term planes show new-hope +agent-term post '!sourceos-build' '@operator' 'AgentTerm is online.' +agent-term record agent-registry agent_identity '!agent-registry' 'Resolve agent.codex before dispatch' +agent-term record prophet-workspace workroom '!prophet-workspace' 'Bind PI demo workroom' --metadata-json '{"workroom":"pi-demo"}' +agent-term record slash-topics topic_scope '!slash-topics' 'Select /professional-intelligence topic scope' +agent-term record memory-mesh memory_recall '!memory-mesh' 'Recall workroom context' --requires-approval +agent-term record new-hope semantic_thread '!new-hope' 'Normalize Matrix thread into New Hope objects' +agent-term record holmes investigation '!holmes' 'Investigate evidence gap' --requires-approval +agent-term sherlock-packet '!sherlock-search' 'hydrate AgentTerm workroom context' --workroom agent-term --topic professional-intelligence --thread-id demo-search +agent-term record meshrush graph_view '!meshrush' 'Enter professional intelligence graph view' --requires-approval +agent-term request-shell '!cloudshell-fog' default --thread-id demo-shell +agent-term tail +agent-term shell +``` + +The first implementation stores events in SQLite and records governance-preserving events locally. Matrix network I/O, Agent Registry resolution/grants/revocation, Sociosphere materialization, Prophet Workspace workroom APIs, Slash Topics membranes, Memory Mesh recall/writeback, New Hope semantic normalization, Holmes request/status/artifact correlation, Sherlock Search hydration, MeshRush graph execution, cloudshell-fog session attach, AgentPlane bundle execution, and Policy Fabric admission are intentionally isolated behind adapter boundaries so the terminal, policy, event log, and registry can be hardened independently. + +## Docs + +- [SourceOS control surface architecture](docs/architecture/sourceos-control-surface.md) +- [Agent Registry integration boundary](docs/integration/agent-registry-boundary.md) +- [Holmes integration boundary](docs/integration/holmes-boundary.md) +- [Agent instructions](AGENTS.md) +- [Example configuration](configs/agent-term.example.json) + +## Repository status + +This is the seed implementation. It is intentionally small but runnable. The next step is to land the Matrix-room MVP, then bind Agent Registry resolution/grants/revocation, Policy Fabric admission, Sociosphere workspace state, Prophet Workspace workrooms, Slash Topics scopes, Memory Mesh recall/writeback, New Hope semantic events, Holmes request/status/artifact correlation, Sherlock Search packets, MeshRush graph events, cloudshell-fog session lifecycle, AgentPlane evidence flow, and Hermes/Codex/Claude Code/OpenCLAW participants under explicit operator permissions. diff --git a/configs/agent-term.example.json b/configs/agent-term.example.json new file mode 100644 index 0000000..feaa9ae --- /dev/null +++ b/configs/agent-term.example.json @@ -0,0 +1,180 @@ +{ + "workspace": "sourceos", + "defaultChannel": "!sourceos-ops", + "eventStore": { + "driver": "sqlite", + "path": ".agent-term/events.sqlite3" + }, + "matrix": { + "enabled": false, + "homeserverUrl": "https://matrix.example.org", + "userId": "@agent-term:example.org", + "deviceName": "agent-term-operator-console", + "rooms": { + "sourceosOps": "!sourceos-ops:example.org", + "sourceosBuild": "!sourceos-build:example.org", + "agentRegistry": "!agent-registry:example.org", + "sociosphere": "!sociosphere:example.org", + "prophetWorkspace": "!prophet-workspace:example.org", + "slashTopics": "!slash-topics:example.org", + "memoryMesh": "!memory-mesh:example.org", + "newHope": "!new-hope:example.org", + "holmes": "!holmes:example.org", + "sherlockSearch": "!sherlock-search:example.org", + "meshrush": "!meshrush:example.org", + "agentplane": "!agentplane:example.org", + "policyFabric": "!policyfabric:example.org", + "cloudshellFog": "!cloudshell-fog:example.org", + "ciFailures": "!ci-failures:example.org" + }, + "requireEncryptedRoomPostureForSensitiveContext": true, + "preserveBridgeMetadata": true, + "preserveRedactions": true, + "preserveMembershipEvents": true + }, + "agentRegistration": { + "requireRegisteredParticipants": true, + "failClosedWhenRegistryUnavailable": true, + "repository": "SocioProphet/agent-registry", + "requiredFor": [ + "hermes", + "codex", + "claudeCode", + "openclaw", + "matrixBots", + "githubBots", + "ciBots", + "mcpTools", + "localProcessAgents" + ] + }, + "planes": { + "agentRegistry": { + "enabled": true, + "repository": "SocioProphet/agent-registry", + "role": "agent-specs-identities-sessions-memories-tool-grants-revocation-and-runtime-authority", + "requireResolutionBeforeParticipantEnablement": true, + "requireToolGrantBeforeAdapterDispatch": true + }, + "sociosphere": { + "enabled": true, + "repository": "SocioProphet/sociosphere", + "role": "meta-workspace-controller", + "requirePolicyApprovalForMaterialization": true + }, + "prophetWorkspace": { + "enabled": true, + "repository": "SocioProphet/prophet-workspace", + "role": "professional-workrooms-and-workspace-product-surface", + "requirePolicyApprovalForContextHydration": true + }, + "slashTopics": { + "enabled": true, + "repository": "SocioProphet/slash-topics", + "role": "governed-topic-scopes-and-policy-membranes", + "requirePolicyApprovalForMembraneApplication": true + }, + "memoryMesh": { + "enabled": true, + "repository": "SocioProphet/memory-mesh", + "role": "governed-recall-writeback-and-context-packs", + "requirePolicyApprovalForRecall": true, + "requirePolicyApprovalForWriteback": true + }, + "newHope": { + "enabled": true, + "repository": "SocioProphet/new-hope", + "role": "semantic-runtime-for-message-thread-claim-citation-membrane-objects", + "requirePolicyApprovalForSemanticMembranes": true + }, + "holmes": { + "enabled": true, + "repository": "SocioProphet/holmes", + "role": "external-language-intelligence-fabric-request-status-artifact-correlation-only", + "requirePolicyApprovalForInvestigation": true, + "mustNotRedefineHolmesSemantics": true + }, + "sherlockSearch": { + "enabled": true, + "repository": "SocioProphet/sherlock-search", + "preferred": true, + "role": "retrieval-search-packets-and-evidence-search", + "requirePolicyApprovalForHydration": true + }, + "legacySherlock": { + "enabled": false, + "repository": "SocioProphet/sherlock", + "ambientToolAccess": false, + "requireExplicitAuthorization": true, + "requirePolicyApproval": true + }, + "meshrush": { + "enabled": true, + "repository": "SocioProphet/meshrush", + "role": "graph-native-agent-runtime-for-graph-views-diffusion-and-crystallization", + "requirePolicyApprovalForGraphOperations": true + }, + "cloudshellFog": { + "enabled": true, + "repository": "SocioProphet/cloudshell-fog", + "baseUrl": "http://localhost:8080", + "defaultProfile": "default", + "defaultTtlSeconds": 3600, + "requirePolicyApproval": true + }, + "agentplane": { + "enabled": true, + "repository": "SocioProphet/agentplane", + "artifactKinds": [ + "ValidationArtifact", + "PlacementDecision", + "PlacementReceipt", + "RunArtifact", + "ReplayArtifact", + "SessionArtifact" + ], + "requirePolicyApprovalForRun": true + }, + "policyFabric": { + "enabled": true, + "repository": "SocioProphet/policy-fabric", + "decisionMode": "admit-before-side-effect", + "recordDenialsAsEvents": true + } + }, + "participants": { + "hermes": { + "enabled": false, + "mode": "gateway", + "requireAgentRegistryResolution": true, + "agentRegistryId": "agent.hermes" + }, + "codex": { + "enabled": false, + "mode": "repo-branch-pr", + "requireAgentRegistryResolution": true, + "agentRegistryId": "agent.codex", + "requirePolicyApprovalForMutation": true + }, + "claudeCode": { + "enabled": false, + "mode": "repo-branch-pr", + "requireAgentRegistryResolution": true, + "agentRegistryId": "agent.claude-code", + "requirePolicyApprovalForMutation": true + }, + "openclaw": { + "enabled": false, + "mode": "local-runtime", + "requireAgentRegistryResolution": true, + "agentRegistryId": "agent.openclaw", + "requirePolicyApprovalForSideEffects": true + }, + "localProcessAgents": { + "enabled": false, + "mode": "developer-only", + "requireAgentRegistryResolution": true, + "disableForSensitiveContext": true + } + } +} diff --git a/docs/architecture/agent-participant-authority.md b/docs/architecture/agent-participant-authority.md new file mode 100644 index 0000000..8c56e1b --- /dev/null +++ b/docs/architecture/agent-participant-authority.md @@ -0,0 +1,64 @@ +# Agent participant authority + +AgentTerm is not the authority for non-human agent identity. It is the terminal operator console and event surface. + +`SocioProphet/agent-registry` is the authority for: + +- agent specs +- agent identities +- runtime sessions +- tool grants +- memory authority +- revocation +- runtime authority + +## Hard invariant + +Every non-human AgentTerm participant must resolve through Agent Registry before enablement. + +This includes: + +- Hermes +- Codex +- Claude Code +- OpenCLAW +- Matrix bots +- GitHub bots acting as agents +- CI bots acting as agents +- MCP-backed tool participants +- local process agents +- future SourceOS agents + +Local config may express desired bindings. It is not runtime authority. + +## Dispatch sequence + +```text +/operator addresses @agent + -> AgentTerm records agent_identity lookup event + -> Agent Registry resolves identity, spec, grants, session, and revocation state + -> Policy Fabric evaluates action/context admission when required + -> AgentTerm dispatches only if registry and policy posture are valid + -> adapter result records agent_id, grant_id, session_id, policy ref, and evidence ref +``` + +## Failure posture + +AgentTerm must fail closed when registry status is: + +- unknown +- missing +- revoked +- expired +- incompatible with requested tool/capability +- unavailable outside explicitly marked local development mode + +Local development mode must not operate on sensitive context or side-effecting commands unless Agent Registry and Policy Fabric checks pass. + +## Relationship to other planes + +Agent Registry does not replace Policy Fabric. Agent Registry answers: “Who is this agent, what session/grants does it hold, and is it still authorized as an agent participant?” + +Policy Fabric answers: “Is this action or context release allowed under current policy?” + +Both gates are required for side-effecting agent work. diff --git a/docs/architecture/sourceos-control-surface.md b/docs/architecture/sourceos-control-surface.md new file mode 100644 index 0000000..d61a858 --- /dev/null +++ b/docs/architecture/sourceos-control-surface.md @@ -0,0 +1,263 @@ +# AgentTerm as the SourceOS control surface + +AgentTerm is the terminal-native operator surface for the SourceOS multi-agent stack. It does not replace Matrix, Sociosphere, Prophet Workspace, Slash Topics, Memory Mesh, New Hope, Holmes, Sherlock Search, MeshRush, cloudshell-fog, AgentPlane, Policy Fabric, GitHub, CI, MCP, Hermes, Codex, Claude Code, or OpenCLAW. It normalizes those systems into an auditable ChatOps event plane with terminal UX. + +## Boundary statement + +AgentTerm owns: + +- terminal-native room/thread UX +- normalized local event log +- slash-command parsing +- operator approval capture +- adapter dispatch boundaries +- integration visibility across SourceOS planes + +AgentTerm does not own: + +- Matrix homeserver semantics +- Sociosphere workspace manifest, lock, topology, or governance registry authority +- Prophet Workspace product/workroom semantics +- Slash Topics topic-pack or membrane schema authority +- Memory Mesh recall/writeback runtime authority +- New Hope semantic runtime authority +- Holmes language intelligence, casefile, retrieval, eval, or guardrail authority +- Sherlock Search search-packet schema authority +- MeshRush graph runtime authority +- cloudshell-fog placement, OIDC, TTL, or PTY enforcement +- AgentPlane bundle execution or replay authority +- Policy Fabric policy authoring, validation, packaging, or release authority +- legacy Sherlock OSINT behavior +- GitHub repository state +- CI workflow execution + +This keeps AgentTerm as an operator console rather than a new monolith. + +## First-class SourceOS planes + +| Plane | Canonical repo/surface | AgentTerm role | +| --- | --- | --- | +| Matrix | Matrix homeserver and bridge topology | canonical ChatOps transport for rooms, events, membership, redactions, bridge metadata, and E2EE posture | +| Sociosphere | `SocioProphet/sociosphere` | meta-workspace controller for manifests, locks, topology, governance registry, validation lanes, and release-readiness orchestration | +| Prophet Workspace | `SocioProphet/prophet-workspace` | Professional Workrooms and workspace product surface for policy-aware workrooms, audit, receipts, and user-facing collaboration surfaces | +| Slash Topics | `SocioProphet/slash-topics` | governed, signed, replayable topic scopes and policy membranes for search/knowledge operations | +| Memory Mesh | `SocioProphet/memory-mesh` | governed recall, writeback, context packs, memoryd runtime, and memory adapters | +| New Hope | `SocioProphet/new-hope` | higher-order semantic runtime for messages, threads, claims, citations, entities, lenses, receptors, membranes, and moderation events | +| Holmes | `SocioProphet/holmes` | language intelligence fabric for casefiles, retrieval, semantic graphs, synthesis, guardrails, evals, and investigative discovery | +| Sherlock Search | `SocioProphet/sherlock-search` | preferred Sherlock integration for search packets and workroom-scoped retrieval context | +| Legacy Sherlock | `SocioProphet/sherlock` | high-friction, policy-gated OSINT adapter only | +| MeshRush | `SocioProphet/meshrush` | graph-native agent runtime for graph views, diffusion, crystallization, traces, and graph evidence | +| cloudshell-fog | `SocioProphet/cloudshell-fog` | fog-first secure shell/session substrate for governed terminal and browser shells | +| AgentPlane | `SocioProphet/agentplane` | execution authority for validate/place/run/evidence/replay flows | +| Policy Fabric | `SocioProphet/policy-fabric` | policy decision point for command dispatch, sensitive context release, and evidence capture | +| GitHub | GitHub app/CLI/API | issue, PR, branch, review, and check state | +| CI | GitHub Actions and other runners | workflow status, logs, artifacts, retry gates | +| MCP | MCP servers/tools | governed capability plane for external tools and context sources | +| Hermes | Hermes-compatible agent gateway | multi-channel/personal agent participant | +| Codex | Codex CLI/cloud agent | code-writing participant under branch/PR/evidence gates | +| Claude Code | Claude Code CLI | codebase reasoning and patch participant under branch/PR/evidence gates | +| OpenCLAW | OpenCLAW/local open runtime | local/open agent runtime inside SourceOS policy envelopes | + +## Control loop + +```text +Matrix room event or local terminal command + -> AgentTerm normalized event + -> Slash Topics scope and New Hope semantic normalization, when applicable + -> Policy Fabric decision point + -> Adapter dispatch + -> Sociosphere / Prophet Workspace / Memory Mesh / Holmes / Sherlock Search / MeshRush + / cloudshell-fog / AgentPlane / GitHub / CI / MCP / agent runtime + -> evidence event + -> Matrix room and local event log +``` + +Side-effecting operations must pass through a decision event before execution. Examples include workspace materialization, sensitive workroom context hydration, memory recall/writeback, semantic membrane application, Holmes investigation, search-packet hydration, graph diffusion/crystallization, shell attach, repo mutation, CI retry, legacy Sherlock OSINT lookup, and AgentPlane bundle execution. + +## Workspace and workroom integration + +Sociosphere and Prophet Workspace are separate authorities: + +- Sociosphere owns canonical multi-repo workspace state: manifest, lock, topology, governance registry, validation lanes, and release readiness. +- Prophet Workspace owns product semantics: Professional Workrooms, workspace capabilities, policy-aware UX, audit, receipts, and collaboration surfaces. + +AgentTerm should bind Matrix rooms and local terminal threads to Professional Workrooms when available, but use Sociosphere when the operator needs workspace materialization, topology, repo roles, or governance status. + +Minimum workroom event metadata: + +- workroom ID or alias +- Matrix room ID and thread/root event +- operator identity +- Slash Topics scope, when selected +- Policy Fabric decision reference for context release +- Memory Mesh context-pack reference, when hydrated +- Sherlock Search packet reference, when retrieval is requested +- Holmes casefile reference, when investigation begins +- AgentPlane bundle/evidence reference, when execution occurs + +## Slash Topics integration + +Slash Topics supplies governed scopes for knowledge and search surfaces. AgentTerm slash commands should preserve: + +- topic-pack ID +- signature or validation state +- policy membrane decision +- room/thread/workroom binding +- deterministic receipt reference +- downstream search, memory, Holmes, Sherlock, or New Hope correlation IDs + +Topic scopes should constrain Memory Mesh recall, Sherlock Search packets, Holmes investigations, New Hope semantic routing, and MeshRush graph view selection. + +## Memory Mesh integration + +Memory Mesh should supply governed recall and writeback. AgentTerm should not treat memory as generic hidden prompt history. + +Minimum memory event metadata: + +- recall or writeback operation +- workroom/thread binding +- topic scope +- policy decision reference +- memory entry IDs or context-pack ID +- source/evidence/provenance references +- downstream agent or investigation recipient + +## New Hope integration + +New Hope is not covered by Sherlock or Holmes. It is the semantic runtime underneath message/thread/claim/citation operations. + +AgentTerm should use New Hope to normalize operator and agent conversations into semantic commons objects before routing to investigation, retrieval, ranking, moderation, or graph operations. + +Minimum New Hope event metadata: + +- source Matrix event/thread/workroom reference +- Message/Thread/Claim/Citation/Entity/Lens/ModerationEvent IDs, when available +- receptor/membrane decision reference +- provenance and replay references +- ranking/moderation output, when applicable + +## Holmes and Sherlock integration + +Holmes and Sherlock Search are complementary: + +- Holmes is the language-intelligence fabric for casefiles, retrieval, semantic graphs, synthesis, guardrails, evals, and investigative discovery. +- Sherlock Search is the discovery/search-packet surface and retrieval-evidence engine. +- Legacy Sherlock is an explicitly authorized, policy-gated OSINT adapter only. + +Preferred investigation path: + +```text +/operator asks for investigation or retrieval + -> AgentTerm binds workroom/topic/thread + -> New Hope normalizes semantic thread objects + -> Policy Fabric evaluates context release and action scope + -> Sherlock Search creates/validates search packet + -> Memory Mesh hydrates approved context pack + -> Holmes opens casefile or investigation workflow + -> AgentTerm records findings, claims, citations, provenance, and evidence +``` + +## MeshRush integration + +MeshRush is the graph-operating runtime. It does not replace the workspace controller, execution control plane, or learning/evaluation plane. + +AgentTerm should expose graph operations as visible events: + +- graph view selection +- diffusion/exploration request +- stop/crystallization decision +- graph artifact persistence +- trace/evidence emission +- downstream AgentPlane or Holmes handoff + +Graph operations should carry provenance, reversibility, policy decision, and workroom/topic/memory bindings. + +## Cloud-fog shell integration + +AgentTerm should request cloudshell-fog sessions instead of directly spawning privileged shells for SourceOS operator work. The minimum session request event needs: + +- operator identity +- Matrix room/channel +- thread/work-order ID +- requested profile +- TTL +- placement hint +- policy decision reference +- resulting session ID and attach URL reference when approved +- audit event correlation ID + +AgentTerm may still provide local development shell commands, but those are not a substitute for SourceOS-governed cloud-fog shell sessions. + +## AgentPlane integration + +AgentTerm should call AgentPlane for execution, not reimplement execution. The minimum event mapping is: + +| AgentPlane concept | AgentTerm event kind | +| --- | --- | +| `ValidationArtifact` | `validation` | +| `PlacementDecision` | `placement` | +| `PlacementReceipt` | `placement` / `evidence` | +| `RunArtifact` | `run` / `evidence` | +| `ReplayArtifact` | `replay` / `evidence` | +| `SessionArtifact` | `session` / `evidence` | + +AgentTerm threads should preserve artifact paths, content hashes, executor identity, bundle ID, policy IDs, and replay inputs. + +## Policy Fabric integration + +Policy Fabric is the decision authority for AgentTerm operations. AgentTerm should emit policy-check events before adapter dispatch and decision/evidence events after validation. + +Minimum policy hooks: + +- command admission +- capability admission +- context-release admission +- workroom context admission +- topic membrane admission +- memory recall/writeback admission +- semantic membrane admission +- investigation admission +- search-packet hydration admission +- graph-operation admission +- shell-session admission +- legacy OSINT admission +- GitHub mutation admission +- CI retry admission +- AgentPlane run admission + +Policy denials must be displayed in-channel with enough metadata for review. + +## Matrix integration + +Matrix room metadata is security-relevant. AgentTerm should preserve: + +- room ID and alias +- event ID +- sender MXID +- thread/root event when available +- membership changes +- redactions +- bridge metadata +- encryption state and verification posture + +Agents should not receive sensitive room history from an encrypted room unless AgentTerm can establish an acceptable E2EE posture. + +## Implementation order + +1. Local event log and CLI shell. +2. Full SourceOS plane registry and CLI shortcuts. +3. Matrix read/write adapter with E2EE posture surfaced. +4. Policy Fabric command-admission stub. +5. Sociosphere workspace state and topology adapter. +6. Prophet Workspace workroom binding and receipt adapter. +7. Slash Topics scope/membrane adapter. +8. Memory Mesh recall/writeback/context-pack adapter. +9. New Hope semantic-thread/message/claim/citation adapter. +10. Sherlock Search packet validation and hydration flow. +11. Holmes casefile/investigation/synthesis/eval flow. +12. MeshRush graph-view/diffusion/crystallization flow. +13. cloudshell-fog session request and attach flow. +14. AgentPlane validate/place/run/evidence flow. +15. GitHub/CI bridge events. +16. Hermes, Codex, Claude Code, and OpenCLAW participant adapters. +17. Textual TUI with rooms, threads, events, approvals, workrooms, context, graph, and evidence panes. diff --git a/docs/integration/agent-registry-boundary.md b/docs/integration/agent-registry-boundary.md new file mode 100644 index 0000000..2271985 --- /dev/null +++ b/docs/integration/agent-registry-boundary.md @@ -0,0 +1,71 @@ +# Agent Registry integration boundary + +AgentTerm must not invent, hardcode, or locally bless non-human agent participants. Agent identity and runtime authority belong to `SocioProphet/agent-registry`. + +Agent Registry is the governed registry for SocioProphet agent specs, identities, sessions, memories, tool grants, revocation, and runtime authority. + +## AgentTerm may own + +- terminal UX for selecting, viewing, and addressing registered agents +- local event records that reference agent identities and sessions +- Matrix room/thread correlation metadata for agent messages +- request events for tool grants, capability use, and session activation +- display of Agent Registry status, grants, revocations, and denials +- adapter plumbing that resolves registered agents before dispatch + +## AgentTerm must not own + +- canonical agent specs +- agent identity authority +- runtime session authority +- tool grant authority +- revocation authority +- memory ownership authority +- capability-policy decisions +- a hidden allowlist that bypasses Agent Registry + +## Required invariant + +Every non-human AgentTerm participant must be registered and resolved through Agent Registry before enablement. This includes: + +- Hermes participants +- Codex participants +- Claude Code participants +- OpenCLAW participants +- Matrix bots +- GitHub bots acting as agents +- CI bots acting as agents +- MCP-backed tool participants +- local process agents +- future custom SourceOS agents + +Local config may reference agents, but config is not authority. Config only expresses desired local bindings. Agent Registry decides whether the participant exists, what it may do, which tools it may use, which sessions are live, and whether any grants have been revoked. + +## Expected flow + +```text +/operator addresses @agent + -> AgentTerm records agent_identity lookup event + -> Agent Registry resolves identity, spec, runtime authority, grants, and session state + -> Policy Fabric evaluates requested action/context release where required + -> AgentTerm dispatches through the appropriate adapter only if identity and grants are valid + -> adapter result is recorded with agent ID, grant ID, session ID, and policy/evidence references + -> Matrix room and local event log receive visible status +``` + +## Minimum metadata on agent events + +- `agent_id` +- `agent_registry_ref` +- `agent_spec_version` +- `session_id`, when active +- `tool_grant_ids`, when tools are requested +- `revocation_check_at` +- `policy_decision_ref`, when the action is side-effecting or context-sensitive +- `adapter_key` +- `runtime_authority` +- `workroom`, `topic_scope`, and `thread_id`, when applicable + +## Adapter posture + +Agent adapters should fail closed when Agent Registry is unavailable or returns unknown/revoked status. The only acceptable exception is a clearly marked local development mode that cannot operate on sensitive context or side-effecting commands. diff --git a/docs/integration/holmes-boundary.md b/docs/integration/holmes-boundary.md new file mode 100644 index 0000000..fe8527c --- /dev/null +++ b/docs/integration/holmes-boundary.md @@ -0,0 +1,44 @@ +# Holmes integration boundary + +AgentTerm must integrate with Holmes without redefining Holmes. + +Holmes is the SocioProphet language-intelligence fabric. AgentTerm is only the terminal-native ChatOps/operator surface that can display, request, correlate, and audit Holmes-related work. + +## AgentTerm may own + +- terminal commands that request a Holmes investigation or casefile operation +- local event records that reference Holmes work +- Matrix room/thread correlation metadata +- Policy Fabric admission records before sensitive context release or side-effecting investigation actions +- links to Holmes artifacts, casefiles, evals, guardrails, retrieval traces, or synthesis outputs +- operator-visible status and evidence summaries + +## AgentTerm must not own + +- Holmes product semantics +- Holmes service implementation +- Holmes casefile schema authority +- Holmes retrieval, NLP, guardrail, eval, or synthesis internals +- Holmes model-routing policy +- Holmes release/deployment topology +- any migration or refactor inside `SocioProphet/holmes` + +## Integration posture + +Holmes integration in AgentTerm should be conservative until the Holmes repo exposes stable contracts. The current AgentTerm registry entry is only an operator-visible integration placeholder based on the public Holmes product boundary. It should not be treated as a normative Holmes specification. + +## Expected flow + +```text +Matrix room / AgentTerm thread + -> optional Prophet Workspace workroom binding + -> optional Slash Topics scope + -> optional New Hope semantic normalization + -> Policy Fabric admission for sensitive context or side effects + -> Sherlock Search packet or Memory Mesh context pack, when needed + -> Holmes casefile/investigation request + -> Holmes-owned artifacts/status/evidence references + -> AgentTerm event log + Matrix-visible summary +``` + +AgentTerm should call or observe Holmes through adapters once Holmes contracts are stable. Until then, AgentTerm should record governed request events and preserve references rather than simulating Holmes behavior. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..9a54d47 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,63 @@ +[build-system] +requires = ["setuptools>=68", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "agent-term" +version = "0.1.0" +description = "Terminal-native Matrix-first ChatOps console for SourceOS multi-agent operations." +readme = "README.md" +requires-python = ">=3.11" +license = { text = "MIT" } +authors = [ + { name = "SourceOS-Linux" } +] +keywords = [ + "sourceos", + "matrix", + "chatops", + "tui", + "agents", + "mcp", + "agentplane", + "policy-fabric", + "sherlock" +] +classifiers = [ + "Development Status :: 2 - Pre-Alpha", + "Environment :: Console", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Topic :: Communications :: Chat", + "Topic :: Software Development :: User Interfaces" +] +dependencies = [] + +[project.optional-dependencies] +dev = [ + "pytest>=8.0", + "ruff>=0.4" +] +textual = [ + "textual>=0.58" +] +matrix = [ + "matrix-nio>=0.24" +] + +[project.scripts] +agent-term = "agent_term.cli:main" + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.ruff] +line-length = 100 +src = ["src", "tests"] + +[tool.pytest.ini_options] +testpaths = ["tests"] +pythonpath = ["src"] diff --git a/src/agent_term/__init__.py b/src/agent_term/__init__.py new file mode 100644 index 0000000..e7ddf3b --- /dev/null +++ b/src/agent_term/__init__.py @@ -0,0 +1,3 @@ +"""AgentTerm package.""" + +__version__ = "0.1.0" diff --git a/src/agent_term/__main__.py b/src/agent_term/__main__.py new file mode 100644 index 0000000..d951c7a --- /dev/null +++ b/src/agent_term/__main__.py @@ -0,0 +1,6 @@ +"""Allow `python -m agent_term` execution.""" + +from agent_term.cli import main + + +raise SystemExit(main()) diff --git a/src/agent_term/adapters.py b/src/agent_term/adapters.py new file mode 100644 index 0000000..0ae22a9 --- /dev/null +++ b/src/agent_term/adapters.py @@ -0,0 +1,116 @@ +"""Adapter contracts for AgentTerm participants. + +Adapters translate between AgentTerm events and concrete systems: Matrix rooms, +Agent Registry, Sociosphere, Prophet Workspace, Slash Topics, Memory Mesh, New Hope, +Holmes, Sherlock Search, MeshRush, cloudshell-fog, AgentPlane, Policy Fabric, Hermes, +Codex, Claude Code, OpenCLAW, GitHub, CI, MCP, and local process agents. + +This file intentionally avoids vendor SDK dependencies. Real network adapters should live behind +this small contract so the terminal UI, local event log, and policy boundary remain stable. +""" + +from __future__ import annotations + +import subprocess +from dataclasses import dataclass, field +from typing import Protocol + +from agent_term.events import AgentTermEvent + + +@dataclass(frozen=True) +class AdapterResult: + """Normalized adapter result emitted back into the AgentTerm event log.""" + + ok: bool + body: str + source: str + kind: str = "adapter_result" + metadata: dict[str, object] = field(default_factory=dict) + + def to_event(self, request: AgentTermEvent, sender: str = "@agent-term") -> AgentTermEvent: + return AgentTermEvent( + channel=request.channel, + sender=sender, + kind=self.kind, + source=self.source, + body=self.body, + thread_id=request.thread_id, + metadata={"request_event_id": request.event_id, **self.metadata}, + ) + + +class AgentTermAdapter(Protocol): + """Protocol implemented by AgentTerm integration adapters.""" + + key: str + + def supports(self, event: AgentTermEvent) -> bool: + """Return true when this adapter can handle the event.""" + + def handle(self, event: AgentTermEvent) -> AdapterResult: + """Handle an event and return a normalized result.""" + + +@dataclass(frozen=True) +class ProcessAdapter: + """Simple command-backed adapter for local agents and CLIs. + + The command is passed to the shell only when explicitly configured by the operator. This is + deliberately not wired to the interactive shell yet; Agent Registry resolution, Policy Fabric + approval, and SourceOS execution envelopes should be inserted before side-effecting commands + are enabled. + """ + + key: str + command: tuple[str, ...] + accepted_kinds: tuple[str, ...] = ("command",) + timeout_seconds: int = 300 + + def supports(self, event: AgentTermEvent) -> bool: + return event.kind in self.accepted_kinds or event.source == self.key + + def handle(self, event: AgentTermEvent) -> AdapterResult: + completed = subprocess.run( + [*self.command, event.body], + check=False, + text=True, + capture_output=True, + timeout=self.timeout_seconds, + ) + output = completed.stdout.strip() or completed.stderr.strip() + return AdapterResult( + ok=completed.returncode == 0, + body=output or f"{self.key} exited with code {completed.returncode}", + source=self.key, + metadata={ + "returncode": completed.returncode, + "command": list(self.command), + "stderr_present": bool(completed.stderr.strip()), + }, + ) + + +ADAPTER_TARGETS = { + "matrix": "Canonical room/event transport adapter; preserve room IDs, event IDs, redactions, membership, bridge metadata, and E2EE posture.", + "agent-registry": "Agent identity and runtime-authority adapter for specs, participants, sessions, tool grants, revocation, memories, and registration state.", + "sociosphere": "Meta-workspace controller adapter for manifest, lock, topology, governance registry, and validation-lane events.", + "prophet-workspace": "Professional Workrooms and workspace product adapter for workroom binding, policy-aware UX, admin, audit, and search surfaces.", + "slash-topics": "Governed topic-scope adapter for signed topic packs, policy membranes, and deterministic receipts.", + "memory-mesh": "Governed memory/context adapter for recall, writeback, context packs, LiteLLM hooks, and OpenCLAW memory tools.", + "new-hope": "Semantic runtime adapter for messages, threads, claims, citations, entities, lenses, receptors, membranes, and moderation events.", + "holmes": "Boundary-respecting Holmes adapter for request/status/artifact correlation only; AgentTerm must not define Holmes behavior.", + "sherlock-search": "Preferred Sherlock integration for scoped search packets and context hydration.", + "legacy-sherlock": "High-friction policy-gated OSINT wrapper only; never a default ambient tool.", + "meshrush": "Graph-operating runtime adapter for graph views, diffusion, crystallization, traces, and graph evidence.", + "cloudshell-fog": "Fog-first shell/session substrate; AgentTerm requests sessions but does not bypass OIDC, placement, TTL, or audit.", + "agentplane": "Execution authority for bundle validation, placement, runs, replay, and evidence artifacts.", + "policy-fabric": "Policy decision and evidence authority for side-effecting commands and sensitive context release.", + "hermes": "Personal/multi-channel agent gateway participant; must resolve identity and grants through Agent Registry before enablement.", + "codex": "Code-writing participant; must be registered and operate through repo branches, diffs, PRs, and approval-gated shell/test commands.", + "claude-code": "Codebase reasoning participant; must be registered and emit plan, diff, command, and evidence events.", + "openclaw": "Local/open agent runtime participant; must be registered and run inside SourceOS capability and policy envelopes.", + "github": "Issue, PR, review, check, branch, and bot-event integration; GitHub bots must be represented in Agent Registry where acting as agents.", + "ci": "Workflow status/log/retry integration with explicit retry approval gates; CI bots must be represented in Agent Registry where acting as agents.", + "mcp": "Tool-plane adapter for files, docs, search, memory, calendar, and other governed capabilities; tool grants should resolve through Agent Registry.", +} diff --git a/src/agent_term/cli.py b/src/agent_term/cli.py new file mode 100644 index 0000000..71d9894 --- /dev/null +++ b/src/agent_term/cli.py @@ -0,0 +1,451 @@ +"""Command-line entry point for AgentTerm.""" + +from __future__ import annotations + +import argparse +import json +import shlex +import sys +from pathlib import Path +from typing import Any + +from agent_term.events import AgentTermEvent +from agent_term.planes import get_plane, iter_planes +from agent_term.store import DEFAULT_DB_PATH, EventStore + + +DEFAULT_CHANNEL = "!sourceos-ops" +APPROVAL_NEXT_AUTHORITY = "policy-fabric" + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + prog="agent-term", + description="Matrix-first terminal ChatOps console for SourceOS multi-agent operations.", + ) + parser.add_argument( + "--db", + default=str(DEFAULT_DB_PATH), + help="Path to the local AgentTerm SQLite event log.", + ) + + subparsers = parser.add_subparsers(dest="command", required=True) + + subparsers.add_parser("init", help="Initialize the local AgentTerm event log.") + + post = subparsers.add_parser("post", help="Append a message/event to the local event log.") + post.add_argument("channel") + post.add_argument("sender") + post.add_argument("body") + post.add_argument("--kind", default="message") + post.add_argument("--source", default="local") + post.add_argument("--thread-id") + post.add_argument("--metadata-json", default="{}") + + record = subparsers.add_parser( + "record", + help="Record a typed SourceOS plane event without invoking a network adapter.", + ) + record.add_argument("plane", help="Registered plane key, e.g. memory-mesh or new-hope.") + record.add_argument("kind", help="Event kind, e.g. workroom, topic_scope, memory_recall.") + record.add_argument("channel") + record.add_argument("body") + record.add_argument("--sender", default="@operator") + record.add_argument("--thread-id") + record.add_argument("--requires-approval", action="store_true") + record.add_argument("--metadata-json", default="{}") + + tail = subparsers.add_parser("tail", help="Show recent events.") + tail.add_argument("channel", nargs="?") + tail.add_argument("--limit", type=int, default=25) + + planes = subparsers.add_parser("planes", help="List or inspect first-class SourceOS planes.") + planes_sub = planes.add_subparsers(dest="planes_command", required=True) + planes_sub.add_parser("list", help="List registered SourceOS planes.") + show_plane = planes_sub.add_parser("show", help="Show one registered SourceOS plane.") + show_plane.add_argument("plane") + + request_shell = subparsers.add_parser( + "request-shell", + help="Record a governed cloudshell-fog shell-session request event.", + ) + request_shell.add_argument("channel") + request_shell.add_argument("profile", nargs="?", default="default") + request_shell.add_argument("--sender", default="@operator") + request_shell.add_argument("--ttl-seconds", type=int, default=3600) + request_shell.add_argument("--placement-hint", default="fog-first") + request_shell.add_argument("--thread-id") + + sherlock_packet = subparsers.add_parser( + "sherlock-packet", + help="Record a governed Sherlock search-packet request event.", + ) + sherlock_packet.add_argument("channel") + sherlock_packet.add_argument("query") + sherlock_packet.add_argument("--sender", default="@operator") + sherlock_packet.add_argument("--workroom", default="default") + sherlock_packet.add_argument("--scope", default="workroom") + sherlock_packet.add_argument("--topic") + sherlock_packet.add_argument("--thread-id") + + subparsers.add_parser("shell", help="Open the minimal AgentTerm interactive shell.") + + return parser + + +def parse_metadata(metadata_json: str) -> dict[str, Any]: + try: + value = json.loads(metadata_json) + except json.JSONDecodeError as exc: + raise SystemExit(f"metadata must be valid JSON: {exc}") from exc + if not isinstance(value, dict): + raise SystemExit("metadata must decode to a JSON object") + return value + + +def format_event(event: AgentTermEvent) -> str: + thread = f" thread={event.thread_id}" if event.thread_id else "" + return ( + f"{event.created_at.isoformat()} [{event.channel}] " + f"{event.sender} kind={event.kind} source={event.source}{thread}: {event.body}" + ) + + +def append_and_print(store: EventStore, event: AgentTermEvent) -> int: + store.append(event) + print(format_event(event)) + if event.metadata.get("approval_required"): + print("status: pending Policy Fabric approval before side effects or sensitive context release") + return 0 + + +def make_plane_event( + *, + plane: str, + kind: str, + channel: str, + sender: str, + body: str, + thread_id: str | None = None, + metadata: dict[str, Any] | None = None, + approval_required: bool = False, +) -> AgentTermEvent: + get_plane(plane) + merged_metadata: dict[str, Any] = { + "plane": plane, + "approval_required": approval_required, + } + if approval_required: + merged_metadata["next_authority"] = APPROVAL_NEXT_AUTHORITY + if metadata: + merged_metadata.update(metadata) + return AgentTermEvent( + channel=channel, + sender=sender, + kind=kind, + source=plane, + body=body, + thread_id=thread_id, + metadata=merged_metadata, + ) + + +def cmd_init(store: EventStore) -> int: + event = AgentTermEvent( + channel=DEFAULT_CHANNEL, + sender="@agent-term", + kind="system", + source="local", + body="AgentTerm local event log initialized.", + ) + store.append(event) + print(f"initialized: {store.path}") + return 0 + + +def cmd_post(store: EventStore, args: argparse.Namespace) -> int: + event = AgentTermEvent( + channel=args.channel, + sender=args.sender, + kind=args.kind, + source=args.source, + body=args.body, + thread_id=args.thread_id, + metadata=parse_metadata(args.metadata_json), + ) + return append_and_print(store, event) + + +def cmd_record(store: EventStore, args: argparse.Namespace) -> int: + event = make_plane_event( + plane=args.plane, + kind=args.kind, + channel=args.channel, + sender=args.sender, + body=args.body, + thread_id=args.thread_id, + metadata=parse_metadata(args.metadata_json), + approval_required=args.requires_approval, + ) + return append_and_print(store, event) + + +def cmd_tail(store: EventStore, args: argparse.Namespace) -> int: + for event in store.tail(channel=args.channel, limit=args.limit): + print(format_event(event)) + return 0 + + +def cmd_planes(args: argparse.Namespace) -> int: + if args.planes_command == "list": + for plane in iter_planes(): + print(f"{plane.key}\t{plane.display_name}\t{plane.repository}") + return 0 + + if args.planes_command == "show": + plane = get_plane(args.plane) + print(f"{plane.display_name} ({plane.key})") + print(f"repo: {plane.repository}") + print(f"source: {plane.source}") + print(f"role: {plane.role}") + print("capabilities:") + for capability in plane.capabilities: + approval = "approval-required" if capability.requires_approval else "no-approval" + kinds = ",".join(capability.event_kinds) or "none" + print(f" - {capability.name} [{approval}; events={kinds}]") + print(f" {capability.description}") + if plane.notes: + print("notes:") + for note in plane.notes: + print(f" - {note}") + return 0 + + raise SystemExit(f"unknown planes command: {args.planes_command}") + + +def cmd_request_shell(store: EventStore, args: argparse.Namespace) -> int: + metadata = { + "profile": args.profile, + "ttl_seconds": args.ttl_seconds, + "placement_hint": args.placement_hint, + } + event = make_plane_event( + plane="cloudshell-fog", + kind="shell_session", + channel=args.channel, + sender=args.sender, + body=f"Request cloudshell-fog session profile={args.profile} placement={args.placement_hint}", + thread_id=args.thread_id, + metadata=metadata, + approval_required=True, + ) + return append_and_print(store, event) + + +def cmd_sherlock_packet(store: EventStore, args: argparse.Namespace) -> int: + metadata = { + "query": args.query, + "workroom": args.workroom, + "scope": args.scope, + "topic": args.topic, + "preferred_repo": "SocioProphet/sherlock-search", + } + event = make_plane_event( + plane="sherlock-search", + kind="search_packet", + channel=args.channel, + sender=args.sender, + body=f"Request Sherlock search packet workroom={args.workroom} scope={args.scope}: {args.query}", + thread_id=args.thread_id, + metadata=metadata, + approval_required=True, + ) + return append_and_print(store, event) + + +def cmd_shell(store: EventStore) -> int: + print("AgentTerm shell. Type /help for commands, /quit to exit.") + channel = DEFAULT_CHANNEL + sender = "@operator" + while True: + try: + line = input(f"{channel} {sender}> ").strip() + except (EOFError, KeyboardInterrupt): + print() + return 0 + if not line: + continue + if line in {"/quit", "/exit"}: + return 0 + if line == "/help": + print("/channel ") + print("/sender ") + print("/tail [limit]") + print("/planes") + print("/workroom ") + print("/topic ") + print("/memory ") + print("/newhope ") + print("/holmes ") + print("/sherlock ") + print("/meshrush ") + print("/request-shell [profile]") + print("/quit") + print("Anything else is posted as a message event.") + continue + if line.startswith("/channel "): + channel = line.split(maxsplit=1)[1] + continue + if line.startswith("/sender "): + sender = line.split(maxsplit=1)[1] + continue + if line.startswith("/tail"): + parts = shlex.split(line) + limit = int(parts[1]) if len(parts) > 1 else 10 + for event in store.tail(channel=channel, limit=limit): + print(format_event(event)) + continue + if line == "/planes": + for plane in iter_planes(): + print(f"{plane.key}\t{plane.display_name}\t{plane.repository}") + continue + if line.startswith("/workroom "): + ref = line.split(maxsplit=1)[1] + event = make_plane_event( + plane="prophet-workspace", + kind="workroom", + channel=channel, + sender=sender, + body=f"Bind AgentTerm thread to Professional Workroom: {ref}", + metadata={"workroom": ref}, + ) + append_and_print(store, event) + continue + if line.startswith("/topic "): + scope = line.split(maxsplit=1)[1] + event = make_plane_event( + plane="slash-topics", + kind="topic_scope", + channel=channel, + sender=sender, + body=f"Select slash-topic scope: {scope}", + metadata={"topic_scope": scope}, + ) + append_and_print(store, event) + continue + if line.startswith("/memory "): + query = line.split(maxsplit=1)[1] + event = make_plane_event( + plane="memory-mesh", + kind="memory_recall", + channel=channel, + sender=sender, + body=f"Request governed Memory Mesh recall: {query}", + metadata={"query": query}, + approval_required=True, + ) + append_and_print(store, event) + continue + if line.startswith("/newhope "): + ref = line.split(maxsplit=1)[1] + event = make_plane_event( + plane="new-hope", + kind="semantic_thread", + channel=channel, + sender=sender, + body=f"Normalize semantic thread/message object: {ref}", + metadata={"semantic_ref": ref}, + ) + append_and_print(store, event) + continue + if line.startswith("/holmes "): + request = line.split(maxsplit=1)[1] + event = make_plane_event( + plane="holmes", + kind="investigation", + channel=channel, + sender=sender, + body=f"Request Holmes investigation: {request}", + metadata={"request": request}, + approval_required=True, + ) + append_and_print(store, event) + continue + if line.startswith("/sherlock "): + query = line.split(maxsplit=1)[1] + event = make_plane_event( + plane="sherlock-search", + kind="search_packet", + channel=channel, + sender=sender, + body=f"Request Sherlock search packet: {query}", + metadata={"query": query}, + approval_required=True, + ) + append_and_print(store, event) + continue + if line.startswith("/meshrush "): + request = line.split(maxsplit=1)[1] + event = make_plane_event( + plane="meshrush", + kind="graph_view", + channel=channel, + sender=sender, + body=f"Request MeshRush graph view: {request}", + metadata={"request": request}, + approval_required=True, + ) + append_and_print(store, event) + continue + if line.startswith("/request-shell"): + parts = shlex.split(line) + profile = parts[1] if len(parts) > 1 else "default" + event = make_plane_event( + plane="cloudshell-fog", + kind="shell_session", + channel=channel, + sender=sender, + body=f"Request cloudshell-fog session profile={profile}", + metadata={"profile": profile}, + approval_required=True, + ) + append_and_print(store, event) + continue + + event = AgentTermEvent(channel=channel, sender=sender, kind="message", source="local", body=line) + append_and_print(store, event) + + +def main(argv: list[str] | None = None) -> int: + parser = build_parser() + args = parser.parse_args(argv) + db_path = Path(args.db) + + if args.command == "planes": + return cmd_planes(args) + + store = EventStore(db_path) + try: + if args.command == "init": + return cmd_init(store) + if args.command == "post": + return cmd_post(store, args) + if args.command == "record": + return cmd_record(store, args) + if args.command == "tail": + return cmd_tail(store, args) + if args.command == "request-shell": + return cmd_request_shell(store, args) + if args.command == "sherlock-packet": + return cmd_sherlock_packet(store, args) + if args.command == "shell": + return cmd_shell(store) + finally: + store.close() + + parser.error(f"unknown command: {args.command}") + return 2 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv[1:])) diff --git a/src/agent_term/events.py b/src/agent_term/events.py new file mode 100644 index 0000000..78ef2a1 --- /dev/null +++ b/src/agent_term/events.py @@ -0,0 +1,77 @@ +"""Canonical AgentTerm event model. + +AgentTerm treats terminal chat, Matrix room events, agent replies, policy decisions, +cloud-fog shell sessions, AgentPlane bundle runs, Sherlock search packets, and GitHub/CI +updates as append-only events. Keeping this model small makes the terminal shell usable now +while leaving enough structure for PolicyFabric and AgentPlane receipts later. +""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from datetime import UTC, datetime +from typing import Any +from uuid import uuid4 + + +@dataclass(frozen=True) +class AgentTermEvent: + """A single normalized ChatOps event. + + Attributes: + event_id: AgentTerm-local stable ID. + channel: Logical channel or Matrix room alias/ID. + sender: Human, agent, bot, or system principal. + kind: Event class such as message, command, decision, run, search, shell_session. + body: Human-readable event body. + thread_id: Optional thread/work-order identifier. + source: Source plane or adapter: matrix, local, agentplane, policy-fabric, sherlock, + cloudshell-fog, github, ci, mcp, hermes, codex, claude-code, openclaw. + metadata: Adapter-specific structured fields. + created_at: UTC timestamp. + """ + + channel: str + sender: str + kind: str + body: str + thread_id: str | None = None + source: str = "local" + metadata: dict[str, Any] = field(default_factory=dict) + event_id: str = field(default_factory=lambda: f"evt_{uuid4().hex}") + created_at: datetime = field(default_factory=lambda: datetime.now(UTC)) + + def to_record(self) -> dict[str, Any]: + return { + "event_id": self.event_id, + "channel": self.channel, + "sender": self.sender, + "kind": self.kind, + "body": self.body, + "thread_id": self.thread_id, + "source": self.source, + "metadata": self.metadata, + "created_at": self.created_at.isoformat(), + } + + @classmethod + def from_record(cls, record: dict[str, Any]) -> "AgentTermEvent": + created_at = record.get("created_at") + if isinstance(created_at, str): + parsed_created_at = datetime.fromisoformat(created_at) + elif isinstance(created_at, datetime): + parsed_created_at = created_at + else: + parsed_created_at = datetime.now(UTC) + + return cls( + event_id=record["event_id"], + channel=record["channel"], + sender=record["sender"], + kind=record["kind"], + body=record["body"], + thread_id=record.get("thread_id"), + source=record.get("source", "local"), + metadata=record.get("metadata") or {}, + created_at=parsed_created_at, + ) diff --git a/src/agent_term/planes.py b/src/agent_term/planes.py new file mode 100644 index 0000000..cb4e063 --- /dev/null +++ b/src/agent_term/planes.py @@ -0,0 +1,276 @@ +"""SourceOS plane registry for AgentTerm. + +These records make SourceOS integration explicit at the CLI/event layer before any +network adapter is enabled. AgentTerm is the operator console; these planes own the +actual agent identity, workspace, semantic, memory, execution, policy, search, shell, +and orchestration semantics. +""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Iterable + + +@dataclass(frozen=True) +class PlaneCapability: + """An operator-visible capability exposed by a SourceOS plane.""" + + name: str + description: str + requires_approval: bool = True + event_kinds: tuple[str, ...] = () + + +@dataclass(frozen=True) +class SourceOSPlane: + """A first-class SourceOS integration plane.""" + + key: str + display_name: str + repository: str + role: str + source: str + capabilities: tuple[PlaneCapability, ...] = field(default_factory=tuple) + notes: tuple[str, ...] = field(default_factory=tuple) + + +SOURCEOS_PLANES: tuple[SourceOSPlane, ...] = ( + SourceOSPlane( + key="matrix", + display_name="Matrix", + repository="external/matrix", + role="Canonical federated ChatOps transport for rooms, events, membership, redactions, bridge metadata, and E2EE posture.", + source="matrix", + capabilities=( + PlaneCapability("room_event_ingest", "Normalize Matrix room events into the AgentTerm event log.", False, ("message", "command")), + PlaneCapability("room_event_emit", "Emit approved agent/operator events back into Matrix rooms.", True, ("message", "decision")), + PlaneCapability("e2ee_posture_check", "Block sensitive context injection when encrypted-room posture is unknown or unsafe.", False, ("policy_check",)), + ), + ), + SourceOSPlane( + key="agent-registry", + display_name="Agent Registry", + repository="SocioProphet/agent-registry", + role="Governed registry for SocioProphet agent specs, identities, sessions, memories, tool grants, revocation, and runtime authority.", + source="agent-registry", + capabilities=( + PlaneCapability("resolve_agent_identity", "Resolve an agent participant identity, spec, runtime authority, and registration state.", False, ("agent_identity",)), + PlaneCapability("validate_agent_registration", "Validate that a requested AgentTerm participant is registered before enablement.", False, ("validation", "agent_identity")), + PlaneCapability("request_tool_grant", "Request a governed tool/capability grant for an agent participant.", True, ("tool_grant", "policy_check")), + PlaneCapability("revoke_agent_session", "Revoke or disable an agent session/capability grant.", True, ("revocation", "decision")), + ), + notes=( + "Every non-human AgentTerm participant must be registered in Agent Registry before it is enabled.", + "Hermes, Codex, Claude Code, OpenCLAW, GitHub bots, CI bots, MCP tools, Matrix bots, and local process agents should resolve identity and tool grants through Agent Registry.", + "AgentTerm local config may reference participants, but Agent Registry remains the runtime authority for agent identity and grants.", + ), + ), + SourceOSPlane( + key="sociosphere", + display_name="Sociosphere", + repository="SocioProphet/sociosphere", + role="Platform meta-workspace controller for canonical workspace manifests, dependency topology, governance registry, validation lanes, and release-readiness orchestration.", + source="sociosphere", + capabilities=( + PlaneCapability("resolve_workspace_manifest", "Resolve canonical workspace repositories, roles, pins, and local overrides.", False, ("workspace_manifest",)), + PlaneCapability("validate_topology", "Validate cross-repo directionality, dependency rules, and governance registry state.", False, ("validation",)), + PlaneCapability("materialize_workspace", "Materialize a governed multi-repo workspace from the canonical manifest.", True, ("workspace_materialization",)), + ), + notes=( + "Sociosphere is the meta-workspace controller; AgentTerm should display and request workspace operations, not own them.", + "AgentTerm events should preserve manifest, lock, topology, and validation evidence references.", + ), + ), + SourceOSPlane( + key="prophet-workspace", + display_name="Prophet Workspace", + repository="SocioProphet/prophet-workspace", + role="Open workspace product suite and Professional Workrooms contract surface for mail, calendar, drive, docs, chat, meetings, policy-aware UX, admin, audit, and search.", + source="prophet-workspace", + capabilities=( + PlaneCapability("open_workroom", "Bind an AgentTerm thread to a Professional Workroom.", False, ("workroom",)), + PlaneCapability("validate_workroom", "Validate Professional Workroom contracts and examples.", False, ("validation",)), + PlaneCapability("hydrate_workspace_context", "Release policy-approved workspace context into an agent thread.", True, ("context_pack", "workroom")), + PlaneCapability("record_workspace_receipt", "Record workspace audit, receipt, and provenance references.", False, ("evidence",)), + ), + notes=( + "Prophet Workspace owns product/workroom semantics; Sociosphere owns meta-workspace manifest and topology.", + "AgentTerm should map Matrix rooms and local threads to Professional Workrooms when available.", + ), + ), + SourceOSPlane( + key="slash-topics", + display_name="Slash Topics", + repository="SocioProphet/slash-topics", + role="Governed, signed, replayable topic scopes for search and knowledge surfaces with policy membranes and deterministic receipts.", + source="slash-topics", + capabilities=( + PlaneCapability("select_topic_scope", "Bind a slash-topic scope to a room, thread, workroom, or query.", False, ("topic_scope",)), + PlaneCapability("validate_topic_pack", "Validate signed topic packs, schemas, and policy membranes.", False, ("validation",)), + PlaneCapability("apply_topic_membrane", "Apply topic-policy gates before search, memory, or context release.", True, ("policy_check", "topic_scope")), + ), + notes=( + "Slash Topics should route and constrain search, memory, Holmes/Sherlock investigation, and New Hope semantic threads.", + "AgentTerm slash commands should preserve topic-pack IDs, signature state, membrane decisions, and replay receipts.", + ), + ), + SourceOSPlane( + key="memory-mesh", + display_name="Memory Mesh", + repository="SocioProphet/memory-mesh", + role="Canonical memory runtime for recall, writeback, config watch, resource application, context packs, LiteLLM callbacks, and OpenCLAW memory tools.", + source="memory-mesh", + capabilities=( + PlaneCapability("recall_context", "Recall scoped memory/context before an agent or investigation run.", True, ("memory_recall", "context_pack")), + PlaneCapability("write_memory", "Write governed memory after an agent action or workroom event.", True, ("memory_write",)), + PlaneCapability("validate_context_pack", "Validate Professional Intelligence context-pack contracts and examples.", False, ("validation",)), + PlaneCapability("record_memory_evidence", "Record memory entries, source references, and evidence records.", False, ("evidence",)), + ), + notes=( + "Memory Mesh should be used for governed recall/writeback, not ad hoc prompt stuffing.", + "Context release should carry workroom, topic, policy, search-packet, and evidence references.", + ), + ), + SourceOSPlane( + key="new-hope", + display_name="New Hope", + repository="SocioProphet/new-hope", + role="Higher-order semantic runtime for agentic commons over messages, threads, claims, citations, entities, lenses, receptors, membranes, and moderation events.", + source="new-hope", + capabilities=( + PlaneCapability("normalize_thread", "Normalize Matrix/workspace/chat threads into New Hope semantic objects.", False, ("semantic_thread",)), + PlaneCapability("extract_claims", "Extract governed claims, citations, entities, and lenses from a thread.", True, ("claim", "citation")), + PlaneCapability("apply_semantic_membrane", "Apply New Hope receptor/membrane decisions before routing or ranking.", True, ("policy_check", "semantic_membrane")), + PlaneCapability("record_moderation_event", "Record moderation/ranking/provenance decisions as replayable semantic events.", False, ("moderation_event", "evidence")), + ), + notes=( + "New Hope is not covered by Holmes or Sherlock; it is the semantic runtime underneath message/thread/claim/citation operations.", + "AgentTerm should use New Hope to normalize operator and agent conversations before routing them into Holmes/Sherlock/Memory Mesh where appropriate.", + ), + ), + SourceOSPlane( + key="holmes", + display_name="Holmes", + repository="SocioProphet/holmes", + role="External language intelligence fabric for casefiles, retrieval, semantic graphs, synthesis, guardrails, evals, and investigative discovery.", + source="holmes", + capabilities=( + PlaneCapability("open_casefile", "Request or correlate a Holmes-owned investigation or casefile.", True, ("casefile",)), + PlaneCapability("investigate", "Request a governed Holmes investigation without redefining Holmes behavior.", True, ("investigation",)), + PlaneCapability("correlate_holmes_artifact", "Record Holmes-owned artifact, status, eval, guardrail, synthesis, or evidence references.", False, ("evidence", "correlation")), + ), + notes=( + "AgentTerm must not define Holmes product semantics, schemas, service behavior, model routing, or deployment.", + "AgentTerm may request, display, correlate, and audit Holmes-owned work once Holmes exposes stable contracts.", + "Holmes integration must respect docs/integration/holmes-boundary.md.", + ), + ), + SourceOSPlane( + key="sherlock-search", + display_name="Sherlock Search", + repository="SocioProphet/sherlock-search", + role="Canonical Sherlock retrieval/search-packet surface for professional intelligence, workroom-scoped context, and demo-grade discovery.", + source="sherlock-search", + capabilities=( + PlaneCapability("create_search_packet", "Create a scoped search packet for a workroom/thread.", True, ("search_packet",)), + PlaneCapability("validate_search_packet", "Validate Sherlock search-packet schema and examples.", False, ("validation",)), + PlaneCapability("hydrate_context_pack", "Hydrate Matrix/AgentPlane/PolicyFabric context for retrieval-aware agent work.", True, ("context_pack",)), + ), + notes=( + "This is the preferred Sherlock integration path for AgentTerm.", + "Search packets should carry provenance, scope, policy state, topic scope, memory references, and workroom/thread binding.", + ), + ), + SourceOSPlane( + key="legacy-sherlock", + display_name="Legacy Sherlock OSINT", + repository="SocioProphet/sherlock", + role="Legacy username/social-network OSINT tool. Available only as a high-friction, policy-gated adapter.", + source="legacy-sherlock", + capabilities=( + PlaneCapability("username_lookup", "Run bounded username discovery with explicit authorization and audit metadata.", True, ("osint_lookup",)), + ), + notes=( + "Do not expose legacy Sherlock as a default agent tool.", + "Use Policy Fabric approvals, scope limits, terms-of-use warnings, and audit receipts before invocation.", + ), + ), + SourceOSPlane( + key="meshrush", + display_name="MeshRush", + repository="SocioProphet/meshrush", + role="Graph-native autonomous-agent protocol and reference runtime for operating over graph views derived from typed hypergraph world models.", + source="meshrush", + capabilities=( + PlaneCapability("enter_graph_view", "Bind an agent or workroom thread to a typed graph view.", True, ("graph_view",)), + PlaneCapability("diffuse_graph", "Run governed graph diffusion/exploration while preserving provenance.", True, ("graph_diffusion",)), + PlaneCapability("crystallize_artifact", "Persist stable local graph structure and derived artifacts.", True, ("graph_artifact", "evidence")), + PlaneCapability("record_graph_trace", "Record traces and learning/evaluation surfaces for adjacent systems.", False, ("trace", "evidence")), + ), + notes=( + "MeshRush is the graph-operating runtime; it does not replace Sociosphere, AgentPlane, Prophet Workspace, or Holmes.", + "AgentTerm should expose graph operations as visible, policy-aware events with provenance and reversibility metadata.", + ), + ), + SourceOSPlane( + key="cloudshell-fog", + display_name="CloudShell Fog", + repository="SocioProphet/cloudshell-fog", + role="Fog-first secure shell/session substrate for browser and terminal-accessible operator runtimes.", + source="cloudshell-fog", + capabilities=( + PlaneCapability("request_shell_session", "Request a governed fog/cloud shell session.", True, ("shell_session", "command")), + PlaneCapability("attach_pty", "Attach to an approved PTY/WebSocket session.", True, ("shell_attach",)), + PlaneCapability("record_shell_audit", "Record session placement, identity, TTL, and audit metadata.", False, ("audit",)), + ), + notes=( + "AgentTerm should not bypass cloudshell-fog policy, placement, OIDC, TTL, or audit semantics.", + "Terminal UX must treat shell attach as a governed event, not an implicit local subprocess.", + ), + ), + SourceOSPlane( + key="agentplane", + display_name="AgentPlane", + repository="SocioProphet/agentplane", + role="Execution control plane for validated bundles, executor placement, runs, replay, and evidence artifacts.", + source="agentplane", + capabilities=( + PlaneCapability("validate_bundle", "Validate a bundle before execution.", False, ("validation",)), + PlaneCapability("select_executor", "Choose an executor and emit a placement decision.", True, ("placement",)), + PlaneCapability("run_bundle", "Run a governed bundle and emit run/replay artifacts.", True, ("run", "evidence")), + PlaneCapability("replay_run", "Replay a governed run from recorded artifacts.", True, ("replay",)), + ), + notes=( + "AgentTerm is the operator surface; AgentPlane remains the execution authority.", + "RunArtifact, ReplayArtifact, ValidationArtifact, and PlacementDecision metadata should be preserved in AgentTerm events.", + ), + ), + SourceOSPlane( + key="policy-fabric", + display_name="Policy Fabric", + repository="SocioProphet/policy-fabric", + role="Policy-as-code control repository for validating, packaging, reviewing, and releasing governed data-protection decisions.", + source="policy-fabric", + capabilities=( + PlaneCapability("evaluate_command", "Evaluate slash commands before dispatch.", False, ("policy_check",)), + PlaneCapability("approve_action", "Bind explicit approval decisions to operator identity and policy context.", True, ("decision",)), + PlaneCapability("emit_policy_evidence", "Persist validation and replay evidence for governed actions.", False, ("evidence",)), + ), + notes=( + "Every side-effecting adapter command should have a Policy Fabric decision point.", + "Policy failures should be visible as first-class ChatOps events, not hidden exceptions.", + ), + ), +) + + +def iter_planes() -> Iterable[SourceOSPlane]: + return iter(SOURCEOS_PLANES) + + +def get_plane(key: str) -> SourceOSPlane: + for plane in SOURCEOS_PLANES: + if plane.key == key: + return plane + raise KeyError(f"unknown SourceOS plane: {key}") diff --git a/src/agent_term/store.py b/src/agent_term/store.py new file mode 100644 index 0000000..ffecd9d --- /dev/null +++ b/src/agent_term/store.py @@ -0,0 +1,109 @@ +"""SQLite-backed local event log for AgentTerm.""" + +from __future__ import annotations + +import json +import sqlite3 +from pathlib import Path +from typing import Iterable + +from agent_term.events import AgentTermEvent + + +DEFAULT_DB_PATH = Path(".agent-term/events.sqlite3") + + +class EventStore: + """Durable append-only store for AgentTerm events.""" + + def __init__(self, path: Path | str = DEFAULT_DB_PATH) -> None: + self.path = Path(path) + self.path.parent.mkdir(parents=True, exist_ok=True) + self._conn = sqlite3.connect(self.path) + self._conn.row_factory = sqlite3.Row + self.init_schema() + + def init_schema(self) -> None: + self._conn.execute( + """ + CREATE TABLE IF NOT EXISTS events ( + event_id TEXT PRIMARY KEY, + channel TEXT NOT NULL, + sender TEXT NOT NULL, + kind TEXT NOT NULL, + body TEXT NOT NULL, + thread_id TEXT, + source TEXT NOT NULL, + metadata_json TEXT NOT NULL, + created_at TEXT NOT NULL + ) + """ + ) + self._conn.execute("CREATE INDEX IF NOT EXISTS idx_events_channel ON events(channel)") + self._conn.execute("CREATE INDEX IF NOT EXISTS idx_events_thread ON events(thread_id)") + self._conn.execute("CREATE INDEX IF NOT EXISTS idx_events_source ON events(source)") + self._conn.commit() + + def append(self, event: AgentTermEvent) -> AgentTermEvent: + self._conn.execute( + """ + INSERT INTO events ( + event_id, channel, sender, kind, body, thread_id, source, metadata_json, created_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + """, + ( + event.event_id, + event.channel, + event.sender, + event.kind, + event.body, + event.thread_id, + event.source, + json.dumps(event.metadata, sort_keys=True), + event.created_at.isoformat(), + ), + ) + self._conn.commit() + return event + + def tail(self, channel: str | None = None, limit: int = 25) -> list[AgentTermEvent]: + if channel: + rows: Iterable[sqlite3.Row] = self._conn.execute( + """ + SELECT * FROM events + WHERE channel = ? + ORDER BY created_at DESC + LIMIT ? + """, + (channel, limit), + ) + else: + rows = self._conn.execute( + """ + SELECT * FROM events + ORDER BY created_at DESC + LIMIT ? + """, + (limit,), + ) + + events = [self._row_to_event(row) for row in rows] + return list(reversed(events)) + + def _row_to_event(self, row: sqlite3.Row) -> AgentTermEvent: + return AgentTermEvent.from_record( + { + "event_id": row["event_id"], + "channel": row["channel"], + "sender": row["sender"], + "kind": row["kind"], + "body": row["body"], + "thread_id": row["thread_id"], + "source": row["source"], + "metadata": json.loads(row["metadata_json"]), + "created_at": row["created_at"], + } + ) + + def close(self) -> None: + self._conn.close() diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 0000000..f14d474 --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,34 @@ +from agent_term.cli import main + + +def test_record_memory_mesh_event(tmp_path, capsys): + db_path = tmp_path / "events.sqlite3" + + exit_code = main( + [ + "--db", + str(db_path), + "record", + "memory-mesh", + "memory_recall", + "!memory-mesh", + "Recall workroom context", + "--requires-approval", + "--metadata-json", + '{"workroom":"pi-demo"}', + ] + ) + + captured = capsys.readouterr() + assert exit_code == 0 + assert "source=memory-mesh" in captured.out + assert "pending Policy Fabric approval" in captured.out + + +def test_planes_show_new_hope(capsys): + exit_code = main(["planes", "show", "new-hope"]) + + captured = capsys.readouterr() + assert exit_code == 0 + assert "New Hope" in captured.out + assert "semantic runtime" in captured.out.lower() diff --git a/tests/test_events_store.py b/tests/test_events_store.py new file mode 100644 index 0000000..4c68890 --- /dev/null +++ b/tests/test_events_store.py @@ -0,0 +1,26 @@ +from agent_term.events import AgentTermEvent +from agent_term.store import EventStore + + +def test_event_store_round_trips_events(tmp_path): + store = EventStore(tmp_path / "events.sqlite3") + try: + event = AgentTermEvent( + channel="!sourceos-build", + sender="@operator", + kind="search_packet", + source="sherlock-search", + body="Request scoped Sherlock packet", + thread_id="thread-1", + metadata={"workroom": "demo", "approval_required": True}, + ) + store.append(event) + + events = store.tail("!sourceos-build", limit=5) + finally: + store.close() + + assert len(events) == 1 + assert events[0].event_id == event.event_id + assert events[0].metadata["approval_required"] is True + assert events[0].source == "sherlock-search" diff --git a/tests/test_planes.py b/tests/test_planes.py new file mode 100644 index 0000000..ad97a4f --- /dev/null +++ b/tests/test_planes.py @@ -0,0 +1,99 @@ +from agent_term.planes import get_plane, iter_planes + + +def test_sourceos_planes_include_required_integrations(): + keys = {plane.key for plane in iter_planes()} + + assert "matrix" in keys + assert "agent-registry" in keys + assert "sociosphere" in keys + assert "prophet-workspace" in keys + assert "slash-topics" in keys + assert "memory-mesh" in keys + assert "new-hope" in keys + assert "holmes" in keys + assert "sherlock-search" in keys + assert "legacy-sherlock" in keys + assert "meshrush" in keys + assert "cloudshell-fog" in keys + assert "agentplane" in keys + assert "policy-fabric" in keys + + +def test_authority_boundaries_are_explicit(): + agent_registry = get_plane("agent-registry") + sociosphere = get_plane("sociosphere") + workspace = get_plane("prophet-workspace") + new_hope = get_plane("new-hope") + holmes = get_plane("holmes") + sherlock = get_plane("sherlock-search") + + assert "agent specs" in agent_registry.role + assert "meta-workspace controller" in sociosphere.role + assert "workroom" in workspace.role.lower() + assert "semantic runtime" in new_hope.role.lower() + assert "language intelligence fabric" in holmes.role.lower() + assert "retrieval/search-packet" in sherlock.role + + +def test_agent_registry_is_participant_authority(): + agent_registry = get_plane("agent-registry") + notes = " ".join(agent_registry.notes).lower() + capabilities = {capability.name for capability in agent_registry.capabilities} + + assert agent_registry.repository == "SocioProphet/agent-registry" + assert "every non-human agentterm participant must be registered" in notes + assert "resolve_agent_identity" in capabilities + assert "validate_agent_registration" in capabilities + assert "request_tool_grant" in capabilities + assert "revoke_agent_session" in capabilities + + +def test_sherlock_search_is_preferred_surface(): + sherlock = get_plane("sherlock-search") + legacy = get_plane("legacy-sherlock") + legacy_contract = f"{legacy.role} {' '.join(legacy.notes)}".lower() + + assert sherlock.repository == "SocioProphet/sherlock-search" + assert "preferred" in " ".join(sherlock.notes).lower() + assert legacy.repository == "SocioProphet/sherlock" + assert "policy-gated" in legacy_contract + assert "default agent tool" in legacy_contract + + +def test_side_effecting_capabilities_require_approval(): + side_effecting = [] + for plane in iter_planes(): + for capability in plane.capabilities: + if capability.name in { + "room_event_emit", + "request_tool_grant", + "revoke_agent_session", + "materialize_workspace", + "hydrate_workspace_context", + "apply_topic_membrane", + "recall_context", + "write_memory", + "extract_claims", + "apply_semantic_membrane", + "open_casefile", + "investigate", + "synthesize_findings", + "run_evals", + "create_search_packet", + "hydrate_context_pack", + "username_lookup", + "enter_graph_view", + "diffuse_graph", + "crystallize_artifact", + "request_shell_session", + "attach_pty", + "select_executor", + "run_bundle", + "replay_run", + "approve_action", + }: + side_effecting.append((plane.key, capability.name, capability.requires_approval)) + + assert side_effecting + assert all(required for _, _, required in side_effecting)