- Create README.md with quick-start examples for producer, consumer, channels, and from_call patterns - Update AGENTS.md Architecture Context: replace all alknet-source ADR references (064, 071, 093, etc.) with alkcall ADR numbers (001..047) - Update AGENTS.md convention references to use alkcall ADR numbers - Restore readme = "README.md" in Cargo.toml Verification: - 542 tests, 0 failed - cargo clippy --all-targets -- -D warnings: clean - cargo fmt --check: clean - cargo doc --no-deps: clean (0 warnings) - cargo publish --dry-run --allow-dirty: succeeds
129 lines
4.4 KiB
Markdown
129 lines
4.4 KiB
Markdown
# alkcall
|
|
|
|
Call + channels RPC: structured JSON operations, streaming subscriptions,
|
|
service discovery, and N-channel multiplexing over one transport stream.
|
|
|
|
This crate unifies the call protocol and the channels protocol, plus the
|
|
vendored core types formerly in `alknet-core`. It is a pure protocol crate
|
|
— no networking, no transport dependencies. Downstream crates (alktty,
|
|
alktunnels, alktrader) compose on top of it.
|
|
|
|
## Quick start
|
|
|
|
### Producer — register an operation and run a dispatcher
|
|
|
|
```rust
|
|
use std::sync::Arc;
|
|
use alkcall::core::{Capabilities, IdentityProvider};
|
|
use alkcall::protocol::{CallAdapter, connection::CallConnection};
|
|
use alkcall::registry::{
|
|
registration::{HandlerRegistration, HandlerKind, OperationRegistry, make_handler},
|
|
spec::{OperationSpec, OperationType, Visibility, AccessControl},
|
|
};
|
|
|
|
let mut registry = OperationRegistry::new();
|
|
registry.register(HandlerRegistration::new(
|
|
OperationSpec::new(
|
|
"echo/run",
|
|
OperationType::Query,
|
|
Visibility::External,
|
|
serde_json::json!({}),
|
|
serde_json::json!({}),
|
|
vec![],
|
|
AccessControl::default(),
|
|
None,
|
|
),
|
|
HandlerKind::Once(make_handler(|input, ctx| async move {
|
|
alkcall::protocol::wire::ResponseEnvelope::ok(ctx.request_id, input)
|
|
})),
|
|
alkcall::registry::registration::OperationProvenance::Local,
|
|
None,
|
|
None,
|
|
Capabilities::new(),
|
|
)).unwrap();
|
|
|
|
let registry = Arc::new(registry);
|
|
let provider: Arc<dyn IdentityProvider> = /* your identity provider */;
|
|
|
|
let adapter = CallAdapter::new(registry, provider);
|
|
// adapter implements ProtocolHandler — call adapter.handle(connection, &auth).await
|
|
```
|
|
|
|
### Consumer — call an operation
|
|
|
|
```rust
|
|
use alkcall::core::Connection;
|
|
use alkcall::protocol::connection::CallConnection;
|
|
|
|
let connection = Connection::from_bidi(
|
|
transport_stream,
|
|
b"alknet/call".to_vec(),
|
|
Some(remote_addr),
|
|
);
|
|
let conn = CallConnection::new(connection);
|
|
|
|
let response = conn.call("echo/run", serde_json::json!({"msg": "hello"})).await;
|
|
assert!(response.result.is_ok());
|
|
```
|
|
|
|
### Channels — open a channel and call through channel 0
|
|
|
|
```rust
|
|
use alkcall::channels::client::ChannelClient;
|
|
use alkcall::core::Connection;
|
|
|
|
let connection = Connection::from_bidi(
|
|
transport_stream,
|
|
b"alknet/channels".to_vec(),
|
|
Some(remote_addr),
|
|
);
|
|
let client = ChannelClient::from_connection(connection).await?;
|
|
|
|
let response = client.call_open_op(
|
|
"echo/run",
|
|
serde_json::json!({"msg": "hello"}),
|
|
).await;
|
|
```
|
|
|
|
### from_call — discover and import remote operations
|
|
|
|
```rust
|
|
use alkcall::client::{from_call, FromCallConfig};
|
|
|
|
let registrations = from_call(&conn, FromCallConfig::new()).await?;
|
|
for reg in registrations {
|
|
conn.register_imported(reg);
|
|
}
|
|
// now call remote ops as if they were local
|
|
let response = conn.call("remote/status", serde_json::json!({})).await;
|
|
```
|
|
|
|
## Architecture
|
|
|
|
alkcall is a pure protocol crate — no networking, no transport
|
|
dependencies. It provides the call and channels protocols as a library.
|
|
Downstream crates compose on top of it in a layered dependency chain.
|
|
|
|
| Role | Call protocol | Channels protocol |
|
|
|------|---------------|-------------------|
|
|
| **Producer** | Registers ops on an `OperationRegistry`, runs a `Dispatcher` | Runs a `ChannelsAdapter`, registers openable ALPNs via `ChannelCore::register_openable` |
|
|
| **Consumer** | Uses `CallConnection` to call ops, uses `from_call` to discover/import remote ops | Uses `ChannelClient` to open channels via `call_open_op` + `open_channel` |
|
|
| **Hub** | Both: runs a `Dispatcher` for ops it produces, holds `CallConnection`s to spokes for ops it consumes | Both: runs a `ChannelsAdapter` for inbound connections, holds `ChannelClient`s to spokes |
|
|
| **Spoke / Worker** | Both: produces ops (its own services), consumes hub ops | Both: produces channels (TTY, tunnel), may consume hub channels |
|
|
|
|
A single process can be a producer of some ops, a consumer of others, a
|
|
channel opener for TTY, and a channel acceptor for tunnels — all on the
|
|
same `alknet/channels` connection.
|
|
|
|
## Documentation
|
|
|
|
- [Architecture docs](docs/architecture/README.md) — the authoritative
|
|
spec: ADRs, wire formats, protocol contracts, and composition patterns.
|
|
- [API docs](https://docs.rs/alkcall) — full crate documentation on docs.rs.
|
|
- [Open questions](docs/architecture/open-questions.md) — tracked
|
|
deferred decisions and feature gaps.
|
|
|
|
## License
|
|
|
|
MIT OR Apache-2.0
|