diff --git a/README.md b/README.md index 8f634d8..7a20fa6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 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. +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, Office artifacts, 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. @@ -11,7 +11,7 @@ The design target is not another single-agent CLI. AgentTerm is the Slack-term c - 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. +- Governance-preserving command shapes for workroom, Office artifact, 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. @@ -22,6 +22,7 @@ The design target is not another single-agent CLI. AgentTerm is the Slack-term c │ Matrix Rooms / Channels │ Thread / Conversation │ │ !agent-registry │ @agent-term: resolve @codex before dispatch │ │ !prophet-workspace │ @operator: /workroom pi-demo │ +│ !prophet-workspace │ @operator: /office create-deck "Demo Deck" │ │ !slash-topics │ @operator: /topic professional-intelligence │ │ !memory-mesh │ @operator: /memory recall workroom context │ │ !new-hope │ @agent-term: semantic thread normalized │ @@ -32,7 +33,7 @@ The design target is not another single-agent CLI. AgentTerm is the Slack-term c │ !policyfabric │ @github: PR #42 checks failing │ │ !cloudshell-fog │ @operator: /request-shell default │ ├──────────────────────────┴─────────────────────────────────────────────┤ -│ /workroom /topic /memory /newhope /holmes /sherlock /meshrush │ +│ /workroom /office /topic /memory /newhope /holmes /sherlock │ └────────────────────────────────────────────────────────────────────────┘ ``` @@ -44,7 +45,7 @@ AgentTerm treats every meaningful action as an event: - 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 +- Prophet Workspace workroom, Office artifact, 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 @@ -83,7 +84,7 @@ AgentTerm should not collapse the SourceOS stack into one generic search agent. | --- | --- | | 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 | +| Prophet Workspace | Workspace product semantics, Professional Workrooms, Office artifacts, 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 | @@ -114,6 +115,11 @@ 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 office create-deck '!prophet-workspace' --workroom workroom-demo-0001 --title 'Professional Intelligence Demo Briefing Deck' +agent-term office create-doc '!prophet-workspace' --workroom workroom-demo-0001 --title 'Professional Intelligence Demo Report' +agent-term office create-sheet '!prophet-workspace' --workroom workroom-demo-0001 --title 'Professional Intelligence Demo Workbook' +agent-term office convert '!prophet-workspace' /workspace/output/demo.docx --to pdf --workroom workroom-demo-0001 +agent-term office inspect '!prophet-workspace' /workspace/output/demo.pptx --workroom workroom-demo-0001 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' @@ -125,16 +131,19 @@ 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. +Office commands record governance-preserving intent events. They do not generate documents, convert files, invoke LibreOffice, operate Collabora/ONLYOFFICE, or send mail directly. Execution delegates to `sourceosctl office ...`; evidence is recorded through AgentPlane. + +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, Office artifact generation/evidence, 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) +- [Prophet Workspace Office Plane boundary](docs/integration/office-plane-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. +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 and Office artifacts, 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/docs/integration/office-plane-boundary.md b/docs/integration/office-plane-boundary.md new file mode 100644 index 0000000..951ed44 --- /dev/null +++ b/docs/integration/office-plane-boundary.md @@ -0,0 +1,68 @@ +# Prophet Workspace Office Plane boundary + +AgentTerm exposes operator-facing Office Plane commands for workroom-bound office artifacts. + +AgentTerm does **not** generate documents, convert files, invoke LibreOffice, operate Collabora/ONLYOFFICE, or send mail directly. It records governance-preserving intent events and delegates execution/evidence to the owning planes. + +## Command surface + +Top-level CLI events: + +```bash +agent-term office create-doc '!prophet-workspace' --workroom workroom-demo-0001 --title 'Demo Report' +agent-term office create-sheet '!prophet-workspace' --workroom workroom-demo-0001 --title 'Demo Workbook' +agent-term office create-deck '!prophet-workspace' --workroom workroom-demo-0001 --title 'Demo Briefing Deck' +agent-term office convert '!prophet-workspace' /workspace/output/demo.docx --to pdf --workroom workroom-demo-0001 +agent-term office inspect '!prophet-workspace' /workspace/output/demo.pptx --workroom workroom-demo-0001 +agent-term office evidence '!prophet-workspace' /workspace/evidence/office.json --workroom workroom-demo-0001 +``` + +Interactive shell commands: + +```text +/office create-doc +/office create-sheet <title> +/office create-deck <title> +/office convert <path> <format> +/office inspect <path> +``` + +## Event model + +All Office commands record `prophet-workspace` events with kind: + +```text +office_artifact_request +``` + +Event metadata includes: + +- workroom id; +- operation; +- OfficeArtifact schema ref; +- expected AgentPlane evidence kind; +- delegated `sourceosctl office ...` command shape; +- policy posture. + +## Responsibility split + +| Plane | Responsibility | +|---|---| +| AgentTerm | Operator command/event surface and Matrix-first ChatOps history. | +| Prophet Workspace | Professional Workroom and OfficeArtifact product semantics. | +| sourceosctl | Local Office dry-run and future execution adapter. | +| AgentPlane | OfficeArtifactEvidence and run/evidence/replay lineage. | +| Policy Fabric | Side-effect approval for send/publish/calendar modifications. | +| Agent Registry | Non-human agent identity and tool grants. | + +## Approval posture + +Office generation and conversion are approval-required in AgentTerm because they can lead to persisted artifacts or external sharing. + +Office inspection and evidence inspection are read-only and do not require approval by default. + +Sending email, publishing externally, or modifying calendars is not implemented here and must remain policy-gated side-effect flow in future slices. + +## Backend posture + +AgentTerm does not care whether an Office artifact is eventually produced by LibreOffice, Collabora, ONLYOFFICE, Microsoft Graph, Google Workspace, or SourceOS-native tooling. Backend selection belongs to Prophet Workspace and the delegated execution surface. diff --git a/src/agent_term/cli.py b/src/agent_term/cli.py index 71d9894..719784b 100644 --- a/src/agent_term/cli.py +++ b/src/agent_term/cli.py @@ -16,6 +16,8 @@ DEFAULT_CHANNEL = "!sourceos-ops" APPROVAL_NEXT_AUTHORITY = "policy-fabric" +OFFICE_ARTIFACT_SCHEMA = "https://socioprophet.io/schemas/workspace/office-artifact.schema.json" +OFFICE_EVIDENCE_KIND = "OfficeArtifactEvidence" def build_parser() -> argparse.ArgumentParser: @@ -88,6 +90,53 @@ def build_parser() -> argparse.ArgumentParser: sherlock_packet.add_argument("--topic") sherlock_packet.add_argument("--thread-id") + office = subparsers.add_parser( + "office", + help="Record governed Prophet Workspace Office Plane requests.", + ) + office_sub = office.add_subparsers(dest="office_command", required=True) + + def add_office_create_common(parser_: argparse.ArgumentParser, artifact_type: str, fmt: str) -> None: + parser_.add_argument("channel") + parser_.add_argument("--sender", default="@operator") + parser_.add_argument("--workroom", default="workroom-local-default") + parser_.add_argument("--title", required=True) + parser_.add_argument("--format", default=fmt) + parser_.add_argument("--thread-id") + parser_.set_defaults(artifact_type=artifact_type) + + create_doc = office_sub.add_parser("create-doc", help="Record an Office document generation request.") + add_office_create_common(create_doc, "document", "docx") + + create_sheet = office_sub.add_parser("create-sheet", help="Record an Office spreadsheet generation request.") + add_office_create_common(create_sheet, "spreadsheet", "xlsx") + + create_deck = office_sub.add_parser("create-deck", help="Record an Office slide deck generation request.") + add_office_create_common(create_deck, "slide-deck", "pptx") + + convert = office_sub.add_parser("convert", help="Record an Office conversion request.") + convert.add_argument("channel") + convert.add_argument("input") + convert.add_argument("--to", required=True) + convert.add_argument("--sender", default="@operator") + convert.add_argument("--workroom", default="workroom-local-default") + convert.add_argument("--title", default="Office conversion") + convert.add_argument("--thread-id") + + inspect = office_sub.add_parser("inspect", help="Record an Office artifact inspection request.") + inspect.add_argument("channel") + inspect.add_argument("path") + inspect.add_argument("--sender", default="@operator") + inspect.add_argument("--workroom", default="workroom-local-default") + inspect.add_argument("--thread-id") + + evidence = office_sub.add_parser("evidence", help="Record an Office evidence inspection request.") + evidence.add_argument("channel") + evidence.add_argument("path") + evidence.add_argument("--sender", default="@operator") + evidence.add_argument("--workroom", default="workroom-local-default") + evidence.add_argument("--thread-id") + subparsers.add_parser("shell", help="Open the minimal AgentTerm interactive shell.") return parser @@ -150,6 +199,38 @@ def make_plane_event( ) +def make_office_event( + *, + channel: str, + sender: str, + workroom: str, + operation: str, + body: str, + thread_id: str | None, + metadata: dict[str, Any], + approval_required: bool, +) -> AgentTermEvent: + merged = { + "workroom": workroom, + "office_operation": operation, + "office_artifact_schema": OFFICE_ARTIFACT_SCHEMA, + "agentplane_evidence_kind": OFFICE_EVIDENCE_KIND, + "delegated_executor": "sourceosctl office", + "policy_posture": "draft/review-first; send/publish requires approval", + } + merged.update(metadata) + return make_plane_event( + plane="prophet-workspace", + kind="office_artifact_request", + channel=channel, + sender=sender, + body=body, + thread_id=thread_id, + metadata=merged, + approval_required=approval_required, + ) + + def cmd_init(store: EventStore) -> int: event = AgentTermEvent( channel=DEFAULT_CHANNEL, @@ -263,6 +344,97 @@ def cmd_sherlock_packet(store: EventStore, args: argparse.Namespace) -> int: return append_and_print(store, event) +def cmd_office(store: EventStore, args: argparse.Namespace) -> int: + if args.office_command in {"create-doc", "create-sheet", "create-deck"}: + sourceosctl_command = [ + "sourceosctl", + "office", + "generate", + "--dry-run", + "--workroom-id", + args.workroom, + "--artifact-type", + args.artifact_type, + "--format", + args.format, + "--title", + args.title, + ] + metadata = { + "artifact_type": args.artifact_type, + "format": args.format, + "title": args.title, + "sourceosctl_command": sourceosctl_command, + } + event = make_office_event( + channel=args.channel, + sender=args.sender, + workroom=args.workroom, + operation="generate", + body=f"Request Office {args.artifact_type} generation for workroom={args.workroom}: {args.title}", + thread_id=args.thread_id, + metadata=metadata, + approval_required=True, + ) + return append_and_print(store, event) + + if args.office_command == "convert": + sourceosctl_command = [ + "sourceosctl", + "office", + "convert", + args.input, + "--to", + args.to, + "--dry-run", + "--workroom-id", + args.workroom, + "--title", + args.title, + ] + event = make_office_event( + channel=args.channel, + sender=args.sender, + workroom=args.workroom, + operation="convert", + body=f"Request Office conversion to {args.to} for workroom={args.workroom}: {args.input}", + thread_id=args.thread_id, + metadata={"input": args.input, "to_format": args.to, "title": args.title, "sourceosctl_command": sourceosctl_command}, + approval_required=True, + ) + return append_and_print(store, event) + + if args.office_command == "inspect": + sourceosctl_command = ["sourceosctl", "office", "inspect", args.path] + event = make_office_event( + channel=args.channel, + sender=args.sender, + workroom=args.workroom, + operation="inspect", + body=f"Request Office artifact inspection for workroom={args.workroom}: {args.path}", + thread_id=args.thread_id, + metadata={"path": args.path, "sourceosctl_command": sourceosctl_command}, + approval_required=False, + ) + return append_and_print(store, event) + + if args.office_command == "evidence": + sourceosctl_command = ["sourceosctl", "office", "evidence", "inspect", args.path] + event = make_office_event( + channel=args.channel, + sender=args.sender, + workroom=args.workroom, + operation="evidence_inspect", + body=f"Request Office evidence inspection for workroom={args.workroom}: {args.path}", + thread_id=args.thread_id, + metadata={"path": args.path, "sourceosctl_command": sourceosctl_command}, + approval_required=False, + ) + return append_and_print(store, event) + + raise SystemExit(f"unknown office command: {args.office_command}") + + def cmd_shell(store: EventStore) -> int: print("AgentTerm shell. Type /help for commands, /quit to exit.") channel = DEFAULT_CHANNEL @@ -283,6 +455,11 @@ def cmd_shell(store: EventStore) -> int: print("/tail [limit]") print("/planes") print("/workroom <id-or-name>") + print("/office create-doc <title>") + print("/office create-sheet <title>") + print("/office create-deck <title>") + print("/office convert <path> <format>") + print("/office inspect <path>") print("/topic <topic-scope>") print("/memory <query>") print("/newhope <thread-or-message-ref>") @@ -321,6 +498,56 @@ def cmd_shell(store: EventStore) -> int: ) append_and_print(store, event) continue + if line.startswith("/office "): + parts = shlex.split(line) + if len(parts) >= 3 and parts[1] in {"create-doc", "create-sheet", "create-deck"}: + artifact_map = { + "create-doc": ("document", "docx"), + "create-sheet": ("spreadsheet", "xlsx"), + "create-deck": ("slide-deck", "pptx"), + } + artifact_type, fmt = artifact_map[parts[1]] + title = " ".join(parts[2:]) + event = make_office_event( + channel=channel, + sender=sender, + workroom="workroom-local-default", + operation="generate", + body=f"Request Office {artifact_type} generation: {title}", + thread_id=None, + metadata={"artifact_type": artifact_type, "format": fmt, "title": title}, + approval_required=True, + ) + append_and_print(store, event) + continue + if len(parts) == 4 and parts[1] == "convert": + event = make_office_event( + channel=channel, + sender=sender, + workroom="workroom-local-default", + operation="convert", + body=f"Request Office conversion to {parts[3]}: {parts[2]}", + thread_id=None, + metadata={"input": parts[2], "to_format": parts[3]}, + approval_required=True, + ) + append_and_print(store, event) + continue + if len(parts) == 3 and parts[1] == "inspect": + event = make_office_event( + channel=channel, + sender=sender, + workroom="workroom-local-default", + operation="inspect", + body=f"Request Office artifact inspection: {parts[2]}", + thread_id=None, + metadata={"path": parts[2]}, + approval_required=False, + ) + append_and_print(store, event) + continue + print("usage: /office create-doc|create-sheet|create-deck <title>; /office convert <path> <format>; /office inspect <path>") + continue if line.startswith("/topic "): scope = line.split(maxsplit=1)[1] event = make_plane_event( @@ -438,6 +665,8 @@ def main(argv: list[str] | None = None) -> int: return cmd_request_shell(store, args) if args.command == "sherlock-packet": return cmd_sherlock_packet(store, args) + if args.command == "office": + return cmd_office(store, args) if args.command == "shell": return cmd_shell(store) finally: diff --git a/tests/test_office_cli.py b/tests/test_office_cli.py new file mode 100644 index 0000000..03f8e30 --- /dev/null +++ b/tests/test_office_cli.py @@ -0,0 +1,74 @@ +from agent_term.cli import main + + +def test_office_create_deck_records_governed_event(tmp_path, capsys): + db_path = tmp_path / "events.sqlite3" + + exit_code = main( + [ + "--db", + str(db_path), + "office", + "create-deck", + "!prophet-workspace", + "--workroom", + "workroom-demo-0001", + "--title", + "Demo Briefing Deck", + ] + ) + + captured = capsys.readouterr() + assert exit_code == 0 + assert "source=prophet-workspace" in captured.out + assert "kind=office_artifact_request" in captured.out + assert "Request Office slide-deck generation" in captured.out + assert "pending Policy Fabric approval" in captured.out + + +def test_office_inspect_records_non_approval_event(tmp_path, capsys): + db_path = tmp_path / "events.sqlite3" + + exit_code = main( + [ + "--db", + str(db_path), + "office", + "inspect", + "!prophet-workspace", + "/workspace/output/demo.pptx", + "--workroom", + "workroom-demo-0001", + ] + ) + + captured = capsys.readouterr() + assert exit_code == 0 + assert "source=prophet-workspace" in captured.out + assert "Office artifact inspection" in captured.out + assert "pending Policy Fabric approval" not in captured.out + + +def test_office_convert_records_governed_event(tmp_path, capsys): + db_path = tmp_path / "events.sqlite3" + + exit_code = main( + [ + "--db", + str(db_path), + "office", + "convert", + "!prophet-workspace", + "/workspace/output/demo.docx", + "--to", + "pdf", + "--workroom", + "workroom-demo-0001", + ] + ) + + captured = capsys.readouterr() + assert exit_code == 0 + assert "source=prophet-workspace" in captured.out + assert "Request Office conversion to pdf" in captured.out + assert "pending Policy Fabric approval" in captured.out